Skip to content

feat(lints): Emit unused_dependencies lint#16600

Merged
ehuss merged 13 commits intorust-lang:masterfrom
epage:unused
Apr 6, 2026
Merged

feat(lints): Emit unused_dependencies lint#16600
ehuss merged 13 commits intorust-lang:masterfrom
epage:unused

Conversation

@epage
Copy link
Copy Markdown
Contributor

@epage epage commented Feb 6, 2026

View all comments

What does this PR try to resolve?

Fixes #15813

[lints.cargo]
unused_dependencies = "warn"

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:

  • activating a feature on a transitive dependency
  • pinning a transitive dependency (instead use target.cfg(false).dependencies)

To handle false positives,

[lints.cargo]
unused_dependencies = { level = "warn", ignore = ["dep_name"] }
  • Some prior art use ignored but I felt like ignore fit in better with allow
  • Per-dep-table is more complicated for users, tricky to get the design right (udeps cares about dep kinds but not target cfgs), these shouldn't be too common, and using dep names gives users control over uniqueness
  • For more background on alternatives, see RFC: Extend manifest dependencies with used rfcs#3920

How 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

  • 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

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.

@rustbot rustbot added A-build-execution Area: anything dealing with executing the compiler A-cargo-targets Area: selection and definition of targets (lib, bins, examples, tests, benches) A-crate-dependencies Area: [dependencies] of any kind A-workspaces Area: workspaces S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 6, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Feb 6, 2026

r? @ehuss

rustbot has assigned @ehuss.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: @ehuss, @epage, @weihanglo
  • @ehuss, @epage, @weihanglo expanded to ehuss, epage, weihanglo
  • Random selection from ehuss, weihanglo

@epage epage marked this pull request as draft February 6, 2026 22:02
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 6, 2026
Comment on lines +102 to +112
[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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our selecting of key-values that use workspace inheritance is not great

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to improve the information provided by toml but it caused other problems, see toml-rs/toml#1119

Comment on lines +45 to +56
[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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +153 to +169
[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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +192 to +195
[dependencies]
unused = "0.1.0"
lib_used = "0.1.0"
bins_used = "0.1.0"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

@joshtriplett joshtriplett Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

Comment on lines +359 to +379
[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 _;
}
"#,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +1017 to +1021
[HELP] remove the dependency
|
11 - bar.path = "../bar"
11 + .path = "../bar"
|
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More problems with how we get spans

Comment thread tests/testsuite/lints/unused_dependencies.rs
@epage
Copy link
Copy Markdown
Contributor Author

epage commented Feb 6, 2026

CC @est31

use crate::lints::get_key_value_span;
use crate::lints::rel_cwd_manifest_path;

pub const LINT: Lint = Lint {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

@epage epage Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked about this in the cargo team meeting.

Options

  • A lint config
  • _ prefix
  • A used = true field with maybe used.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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

@epage epage Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/Boshen/cargo-shear

[package.metadata.cargo-shear]
ignored = ["crate-name"]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to rust-lang/rfcs#3920

continue;
}

for (ext, dependency) in &state.externs {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#8437 would automatically skip any dependency of the form:

_foo = { package = "foo", ...}

I removed that but we can consider adding it back in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #16600 (comment) for a potential use case

Comment on lines +130 to +140
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());
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@T-256
Copy link
Copy Markdown

T-256 commented Feb 9, 2026

side-question (if is acceptable): rust-lang/rust#143856 (comment)

@weihanglo
Copy link
Copy Markdown
Member

@T-256 see rust-lang/rust#143856 (comment).

@epage epage force-pushed the unused branch 2 times, most recently from b60a278 to bb24b5e Compare February 9, 2026 18:39
epage added 5 commits April 3, 2026 10:19
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
@rustbot rustbot added the A-manifest Area: Cargo.toml issues label Apr 3, 2026
@epage epage marked this pull request as ready for review April 3, 2026 17:41
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 3, 2026
@epage
Copy link
Copy Markdown
Contributor Author

epage commented Apr 3, 2026

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 [lints.cargo] is not stable.

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

  • We don't support inherit-but-override yet
  • These probably shouldn't be put in [workspace.lints]

I do wonder if ignore should just be allow...

Copy link
Copy Markdown
Contributor

@ehuss ehuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

View changes since this review

@ehuss ehuss added this pull request to the merge queue Apr 6, 2026
@epage epage mentioned this pull request Apr 6, 2026
17 tasks
@epage
Copy link
Copy Markdown
Contributor Author

epage commented Apr 6, 2026

I added it to the tracking issue #12235

Merged via the queue into rust-lang:master with commit 906fa6c Apr 6, 2026
29 checks passed
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 6, 2026
@epage epage deleted the unused branch April 6, 2026 21:34
rust-bors Bot pushed a commit to rust-lang/rust that referenced this pull request Apr 8, 2026
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
@rustbot rustbot added this to the 1.96.0 milestone Apr 8, 2026
github-actions Bot pushed a commit to rust-lang/rustc-dev-guide that referenced this pull request Apr 13, 2026
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
pull Bot pushed a commit to quansat775-ux/cargo that referenced this pull request Apr 23, 2026
### 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-build-execution Area: anything dealing with executing the compiler A-cargo-targets Area: selection and definition of targets (lib, bins, examples, tests, benches) A-crate-dependencies Area: [dependencies] of any kind A-documenting-cargo-itself Area: Cargo's documentation A-manifest Area: Cargo.toml issues A-workspaces Area: workspaces S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lint for unused [dependencies]

7 participants