Skip to content
Merged
3 changes: 2 additions & 1 deletion src/cargo/core/compiler/job_queue/job_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::core::compiler::locking::LockKey;
use crate::core::compiler::timings::SectionTiming;
use crate::util::Queue;
use crate::util::context::WarningHandling;
use crate::util::interning::InternedString;
use crate::{CargoResult, core::compiler::locking::LockManager};

use super::{Artifact, DiagDedupe, Job, JobId, Message};
Expand Down Expand Up @@ -224,7 +225,7 @@ impl<'a, 'gctx> JobState<'a, 'gctx> {
///
/// This is useful for checking unused dependencies.
/// Should only be called once, as the compiler only emits it once per compilation.
pub fn unused_externs(&self, unused_externs: Vec<String>) {
pub fn unused_externs(&self, unused_externs: std::collections::BTreeSet<InternedString>) {
self.messages
.push(Message::UnusedExterns(self.id, unused_externs));
}
Expand Down
3 changes: 2 additions & 1 deletion src/cargo/core/compiler/job_queue/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ use crate::util::CargoResult;
use crate::util::context::WarningHandling;
use crate::util::diagnostic_server::{self, DiagnosticPrinter};
use crate::util::errors::AlreadyPrintedError;
use crate::util::interning::InternedString;
use crate::util::machine_message::{self, Message as _};
use crate::util::{self, internal};
use crate::util::{DependencyQueue, GlobalContext, Progress, ProgressStyle, Queue};
Expand Down Expand Up @@ -387,7 +388,7 @@ enum Message {
Finish(JobId, Artifact, CargoResult<()>),
FutureIncompatReport(JobId, Vec<FutureBreakageItem>),
SectionTiming(JobId, SectionTiming),
UnusedExterns(JobId, Vec<String>),
UnusedExterns(JobId, std::collections::BTreeSet<InternedString>),
}

impl<'gctx> JobQueue<'gctx> {
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2399,7 +2399,7 @@ fn on_stderr_line_inner(

#[derive(serde::Deserialize)]
struct UnusedExterns {
unused_extern_names: Vec<String>,
unused_extern_names: std::collections::BTreeSet<InternedString>,
}
if let Ok(uext) = serde_json::from_str::<UnusedExterns>(compiler_message.get()) {
trace!(
Expand Down
116 changes: 72 additions & 44 deletions src/cargo/core/compiler/unused_deps.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::BTreeSet;

use cargo_util_schemas::manifest;
use cargo_util_terminal::report::AnnotationKind;
use cargo_util_terminal::report::Group;
Expand Down Expand Up @@ -84,7 +86,7 @@ impl UnusedDepState {
.or_default()
.entry(dep_kind)
.or_default();
*state.needed_units.get_or_insert_default() += 1;
state.needed_units += 1;
for dep in build_runner.unit_deps(root).iter() {
trace!(
" => {} (deps={})",
Expand All @@ -111,7 +113,11 @@ impl UnusedDepState {
Self { states }
}

pub fn record_unused_externs_for_unit(&mut self, unit: &Unit, unused_externs: Vec<String>) {
pub fn record_unused_externs_for_unit(
&mut self,
unit: &Unit,
unused_externs: BTreeSet<InternedString>,
) {
let pkg_id = unit.pkg.package_id();
let dep_kind = dep_kind_of(unit);
trace!(
Expand All @@ -125,11 +131,12 @@ impl UnusedDepState {
.or_default()
.entry(dep_kind)
.or_default();
state
.unused_externs
.entry(unit.clone())
.or_default()
.extend(unused_externs.into_iter().map(|s| InternedString::new(&s)));
state.seen_units.push(unit.clone());
if let Some(existing) = state.unused_externs.as_mut() {
existing.retain(|ext| unused_externs.contains(ext));
} else {
state.unused_externs = Some(unused_externs);
}
Comment on lines +134 to +139

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is a bit weird that the actual core part of this PR is this commit not the fix commit, which people may easily overlook.

The commit message around also doesn't say anything more obvious than "track as we go". It is indeed a refactor commit, though I think it might be worthy adding more context about why this is needed and how this data model unblock the later behavior change.

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 focus PR titles and descriptions on the user-facing impact.

Originally, the intersection was my end-goal. Everything else was "oh, here is another thing I can do". I did not predict at all where it was going to go. I can edit it in hindsight but that also feels weird (wouldn't have been there if I had split the PR like I considered).

I've at least gone into more detail on the commit itself about my intent with it.

}

#[instrument(skip_all)]
Expand Down Expand Up @@ -162,7 +169,7 @@ impl UnusedDepState {

if lint_level == LintLevel::Allow {
for (dep_kind, state) in states.iter() {
for ext in state.unused_externs.values().flatten() {
for ext in state.unused_externs.iter().flatten() {
debug!(
"pkg {} v{} ({dep_kind:?}): ignoring unused extern `{ext}`, lint is allowed",
pkg_id.name(),
Expand All @@ -176,47 +183,60 @@ impl UnusedDepState {
let manifest_path = rel_cwd_manifest_path(pkg.manifest_path(), build_runner.bcx.gctx);
let mut lint_count = 0;
for (dep_kind, state) in states.iter() {
let Some(needed_units) = state.needed_units else {
// not one we care to report
for ext in state.unused_externs.values().flatten() {
for ext in state.unused_externs.iter().flatten() {
let mut used_in_dev = false;
match dep_kind {
DepKind::Normal => {
if let Some(state) = states.get(&DepKind::Development)
&& state
.unused_externs
.as_ref()
.is_some_and(|ue| !ue.contains(ext))
{
used_in_dev = true;
}
}
DepKind::Development => {
if let Some(state) = states.get(&DepKind::Normal)
&& state.externs.contains_key(ext)
{
trace!(
"pkg {} v{} ({dep_kind:?}): ignoring unused extern `{ext}`, inherited from normal dependency",
pkg_id.name(),
pkg_id.version(),
);
continue;
}
}
DepKind::Build => {}
}
let Some(extern_state) = state.externs.get(ext) else {
// not one we care to report
debug!(
"pkg {} v{} ({dep_kind:?}): ignoring unused extern `{ext}`, untracked dependent",
pkg_id.name(),
pkg_id.version(),
);
}
continue;
};
if state.unused_externs.len() != needed_units {
// Some compilations errored without printing the unused externs.
// Don't print the warning in order to reduce false positive
// spam during errors.
for ext in state.unused_externs.values().flatten() {
continue;
};
if state.seen_units.len() != state.needed_units {
debug_assert_ne!(
state.externs.len(),
0,
"assumes tracked is checked first"
);
// Some compilations errored without printing the unused externs.
// Don't print the warning in order to reduce false positive
// spam during errors.
debug!(
"pkg {} v{} ({dep_kind:?}): ignoring unused extern `{ext}`, {} outstanding units",
pkg_id.name(),
pkg_id.version(),
needed_units - state.unused_externs.len()
);
}
continue;
}

for (ext, extern_state) in &state.externs {
if state
.unused_externs
.values()
.any(|unused| !unused.contains(ext))
{
trace!(
"pkg {} v{} ({dep_kind:?}): extern {} is used",
pkg_id.name(),
pkg_id.version(),
ext
state.needed_units - state.seen_units.len()
);
continue;
}
if is_transitive_dep(&extern_state.unit, &state.unused_externs, build_runner) {
if is_transitive_dep(&extern_state.unit, &state.seen_units, build_runner) {
debug!(
"pkg {} v{} ({dep_kind:?}): ignoring unused extern `{ext}`, may be activating features",
pkg_id.name(),
Expand Down Expand Up @@ -273,6 +293,12 @@ impl UnusedDepState {
);
report.push(help);
}
if used_in_dev {
let help = Group::with_title(Level::HELP.secondary_title(
"to still use for development builds, move to `dev-dependencies`",
));
report.push(help);
}

if lint_level.is_warn() {
*warn_count += 1;
Expand All @@ -296,7 +322,7 @@ impl UnusedDepState {
let state = self.states.get(pkg_id)?;
let mut iter = state.values();
let state = iter.next()?;
let mut iter = state.unused_externs.keys();
let mut iter = state.seen_units.iter();
let unit = iter.next()?;
Some(&unit.pkg)
}
Expand All @@ -307,13 +333,15 @@ impl UnusedDepState {
struct DependenciesState {
/// All declared dependencies
externs: IndexMap<InternedString, ExternState>,
/// Expected [`Self::unused_externs`] entries to know we've received them all
/// Expected [`Self::seen_units`] entries to know we've received them all
///
/// To avoid warning in cases where we didn't,
/// e.g. if a [`Unit`] errored and didn't report unused externs.
needed_units: Option<usize>,
/// As reported by rustc
unused_externs: IndexMap<Unit, Vec<InternedString>>,
needed_units: usize,
/// Units that have reported their unused externs
seen_units: Vec<Unit>,
/// Intersection of unused externs across all [`Self::seen_units`]
unused_externs: Option<BTreeSet<InternedString>>,
Comment thread
epage marked this conversation as resolved.
}

#[derive(Clone)]
Expand Down Expand Up @@ -350,11 +378,11 @@ fn unit_desc(unit: &Unit) -> String {
#[instrument(skip_all)]
fn is_transitive_dep(
direct_dep_unit: &Unit,
unused_externs: &IndexMap<Unit, Vec<InternedString>>,
seen_units: &Vec<Unit>,
build_runner: &mut BuildRunner<'_, '_>,
) -> bool {
let mut queue = std::collections::VecDeque::new();
for root_unit in unused_externs.keys() {
for root_unit in seen_units {
for unit_dep in build_runner.unit_deps(root_unit) {
if root_unit.pkg.package_id() == unit_dep.unit.pkg.package_id() {
continue;
Expand Down
23 changes: 22 additions & 1 deletion tests/testsuite/lints/unused_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ fn unused_dep_normal_but_implicit_used_dep_dev() {
"tests/foo.rs",
r#"
#[test]
fn foo {
fn foo() {
Comment thread
epage marked this conversation as resolved.
use used_dev as _;
}
"#,
Expand Down Expand Up @@ -402,6 +402,27 @@ fn unused_dep_normal_but_implicit_used_dep_dev() {
|
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
.run();

p.cargo("check -Zcargo-lints --all-targets")
.masquerade_as_nightly_cargo(&["cargo-lints"])
.with_stderr_data(str![[r#"
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[WARNING] unused dependency
--> Cargo.toml:9:13
|
9 | used_dev = "0.1.0"
| ^^^^^^^^^^^^^^^^^^
|
= [NOTE] `cargo::unused_dependencies` is set to `warn` in `[lints]`
[HELP] remove the dependency
|
9 - used_dev = "0.1.0"
|
[HELP] to still use for development builds, move to `dev-dependencies`
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
.run();
}
Expand Down
Loading