Skip to content

feat(lints): Emit unused_dependencies lint#16600

Draft
epage wants to merge 10 commits intorust-lang:masterfrom
epage:unused
Draft

feat(lints): Emit unused_dependencies lint#16600
epage wants to merge 10 commits intorust-lang:masterfrom
epage:unused

Conversation

@epage
Copy link
Contributor

@epage epage commented Feb 6, 2026

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)

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

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

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

@epage
Copy link
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
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
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
Contributor Author

Choose a reason for hiding this comment

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

Copy link
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
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
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
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
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
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
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
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

T-256 commented Feb 9, 2026

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

@weihanglo
Copy link
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
@rustbot

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author. label Feb 9, 2026
Copy link
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.

Just a partial review for now, still working through this.

It would be helpful to have some doc comments on some of the new types and pub functions just to give them some context.

View changes since this review

}

if build_runner.bcx.gctx.cli_unstable().cargo_lints {
base.arg("-Wunused_crate_dependencies");
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this intentionally placed after the call to extra_args_for? I'm just wondering about the interaction of things like cargo rustc -- -Dwarnings or other such flags that could conflict with this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My intention was to have this very last so that any user provided modification of unused_crate_dependencies would be overridden unless it was in source code which we can't do anything about.

If there are some particular cases you'd like covered to see how they behave, I can add them.

est31 and others added 10 commits February 10, 2026 13:35
This refactors the drain_the_queue function to return a
tuple containing Result. That way, it's still not
possible to use ? or try! to handle errors,
but for readers of the function declaration it's
clearer now that the error actually indicates one.

Bonus: it makes the calling code of drain_the_queue simpler.
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
@epage
Copy link
Contributor Author

epage commented Feb 10, 2026

It would be helpful to have some doc comments on some of the new types and pub functions just to give them some context.

I've added doc comments where I thought they may be helpful

@epage epage changed the title feat(lints): Emit the rest of unused_dependencies feat(lints): Emit unused_dependencies lint Feb 10, 2026
Copy link
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.

Looks good! I think this will be really useful. I don't have any particularly helpful review comments. I imagine as we get more people to test it out we can learn more.

I don't know if you want someone else to take a better look?

View changes since this review

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm assuming the changes in this file are mostly unrelated. Do we want to alter these tests so they don't trigger cargo::unused_dependencies?

Comment on lines +151 to +154
if lint_level.is_error() {
*error_count += 1;
}
gctx.shell().print_report(&report, lint_level.force())?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a side note, I'm not sure if ya'll have considered having the emitter responsible for tracking the error_count so that it wouldn't be necessary to manually manage the count (similar to DiagCtxt in rustc).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is something worth exploring as we evolve the linting systec. I'd prefer it not to be on GlobalContext. As we'd need to track which package we are in, we can also use it to determine when the emitted reason should be shown.

@epage
Copy link
Contributor Author

epage commented Feb 11, 2026

Looks good! I think this will be really useful. I don't have any particularly helpful review comments. I imagine as we get more people to test it out we can learn more.

Thanks for looking! I appreciate the extra eyes on the compilation process and do expect hands on experience will help with what we want the experience to be. Tempted to start this in the nursery group just so we don't have to wait for it to be fully polished (e.g. dealing with false positives).

@est31
Copy link
Member

est31 commented Feb 15, 2026

Thanks @epage for doing this. Nice work, especially love that it prints spans in Cargo.toml.

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

6 participants