diff --git a/src/cargo/core/compiler/unused_deps.rs b/src/cargo/core/compiler/unused_deps.rs index 39b27f724da..519bd6cb6ea 100644 --- a/src/cargo/core/compiler/unused_deps.rs +++ b/src/cargo/core/compiler/unused_deps.rs @@ -98,7 +98,13 @@ impl UnusedDepState { } else { continue; }; - state.externs.insert(dep.extern_crate_name, manifest_deps); + state.externs.insert( + dep.extern_crate_name, + ExternState { + unit: dep.unit.clone(), + manifest_deps, + }, + ); } } @@ -213,7 +219,7 @@ impl UnusedDepState { continue; } - for (ext, dependency) in &state.externs { + for (ext, extern_state) in &state.externs { if state .unused_externs .values() @@ -227,9 +233,17 @@ impl UnusedDepState { ); continue; } + if is_transitive_dep(&extern_state.unit, &state.unused_externs, build_runner) { + debug!( + "pkg {} v{} ({dep_kind:?}): ignoring unused extern `{ext}`, may be activating features", + pkg_id.name(), + pkg_id.version(), + ); + continue; + } // Implicitly added dependencies (in the same crate) aren't interesting - let dependency = if let Some(dependency) = dependency { + let dependency = if let Some(dependency) = &extern_state.manifest_deps { dependency } else { continue; @@ -318,7 +332,7 @@ impl UnusedDepState { #[derive(Default)] struct DependenciesState { /// All declared dependencies - externs: IndexMap>>, + externs: IndexMap, /// Expected [`Self::unused_externs`] entries to know we've received them all /// /// To avoid warning in cases where we didn't, @@ -328,6 +342,12 @@ struct DependenciesState { unused_externs: IndexMap>, } +#[derive(Clone)] +struct ExternState { + unit: Unit, + manifest_deps: Option>, +} + fn dep_kind_of(unit: &Unit) -> DepKind { match unit.target.kind() { TargetKind::Lib(_) => match unit.mode { @@ -352,3 +372,34 @@ fn unit_desc(unit: &Unit) -> String { unit.mode, ) } + +#[instrument(skip_all)] +fn is_transitive_dep( + direct_dep_unit: &Unit, + unused_externs: &IndexMap>, + build_runner: &mut BuildRunner<'_, '_>, +) -> bool { + let mut queue = std::collections::VecDeque::new(); + for root_unit in unused_externs.keys() { + for unit_dep in build_runner.unit_deps(root_unit) { + if root_unit.pkg.package_id() == unit_dep.unit.pkg.package_id() { + continue; + } + if unit_dep.unit == *direct_dep_unit { + continue; + } + queue.push_back(&unit_dep.unit); + } + } + + while let Some(dep_unit) = queue.pop_front() { + for unit_dep in build_runner.unit_deps(dep_unit) { + if unit_dep.unit == *direct_dep_unit { + return true; + } + queue.push_back(&unit_dep.unit); + } + } + + false +} diff --git a/tests/testsuite/lints/unused_dependencies.rs b/tests/testsuite/lints/unused_dependencies.rs index 8c7b915d3b8..664950f6ef0 100644 --- a/tests/testsuite/lints/unused_dependencies.rs +++ b/tests/testsuite/lints/unused_dependencies.rs @@ -968,7 +968,9 @@ fn package_selection() { Package::new("used_bar", "0.1.0").publish(); Package::new("used_foo", "0.1.0").publish(); Package::new("used_external", "0.1.0").publish(); - Package::new("unused", "0.1.0").publish(); + Package::new("unused_bar", "0.1.0").publish(); + Package::new("unused_foo", "0.1.0").publish(); + Package::new("unused_external", "0.1.0").publish(); let p = project() .file( "Cargo.toml", @@ -988,7 +990,7 @@ fn package_selection() { edition = "2018" [dependencies] - unused = "0.1.0" + unused_foo = "0.1.0" used_foo = "0.1.0" bar.path = "../bar" external.path = "../external" @@ -1013,7 +1015,7 @@ fn package_selection() { edition = "2018" [dependencies] - unused = "0.1.0" + unused_bar = "0.1.0" used_bar = "0.1.0" [lints.cargo] @@ -1036,7 +1038,7 @@ fn package_selection() { edition = "2018" [dependencies] - unused = "0.1.0" + unused_external = "0.1.0" used_external = "0.1.0" [lints.cargo] @@ -1056,30 +1058,33 @@ fn package_selection() { .with_stderr_data( str![[r#" [UPDATING] `dummy-registry` index +[LOCKING] 7 packages to latest compatible versions [DOWNLOADING] crates ... -[CHECKING] bar v0.1.0 ([ROOT]/foo/bar) -[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s -[LOCKING] 5 packages to latest compatible versions -[DOWNLOADED] used_foo v0.1.0 (registry `dummy-registry`) -[DOWNLOADED] used_external v0.1.0 (registry `dummy-registry`) +[DOWNLOADED] unused_bar v0.1.0 (registry `dummy-registry`) +[DOWNLOADED] unused_external v0.1.0 (registry `dummy-registry`) +[DOWNLOADED] unused_foo v0.1.0 (registry `dummy-registry`) [DOWNLOADED] used_bar v0.1.0 (registry `dummy-registry`) -[DOWNLOADED] unused v0.1.0 (registry `dummy-registry`) -[CHECKING] unused v0.1.0 +[DOWNLOADED] used_external v0.1.0 (registry `dummy-registry`) +[DOWNLOADED] used_foo v0.1.0 (registry `dummy-registry`) [CHECKING] used_bar v0.1.0 +[CHECKING] unused_external v0.1.0 [CHECKING] used_external v0.1.0 +[CHECKING] unused_bar v0.1.0 [CHECKING] used_foo v0.1.0 +[CHECKING] unused_foo v0.1.0 +[CHECKING] bar v0.1.0 ([ROOT]/foo/bar) [CHECKING] external v0.1.0 ([ROOT]/foo/external) [CHECKING] foo v0.1.0 ([ROOT]/foo/foo) [WARNING] unused dependency --> bar/Cargo.toml:9:13 | -9 | unused = "0.1.0" - | ^^^^^^^^^^^^^^^^ +9 | unused_bar = "0.1.0" + | ^^^^^^^^^^^^^^^^^^^^ | = [NOTE] `cargo::unused_dependencies` is set to `warn` in `[lints]` [HELP] remove the dependency | -9 - unused = "0.1.0" +9 - unused_bar = "0.1.0" | [WARNING] unused dependency --> foo/Cargo.toml:11:13 @@ -1107,13 +1112,14 @@ fn package_selection() { [WARNING] unused dependency --> foo/Cargo.toml:9:13 | -9 | unused = "0.1.0" - | ^^^^^^^^^^^^^^^^ +9 | unused_foo = "0.1.0" + | ^^^^^^^^^^^^^^^^^^^^ | [HELP] remove the dependency | -9 - unused = "0.1.0" +9 - unused_foo = "0.1.0" | +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), @@ -1124,7 +1130,6 @@ fn package_selection() { .masquerade_as_nightly_cargo(&["cargo-lints"]) .with_stderr_data( str![[r#" -[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [WARNING] unused dependency --> foo/Cargo.toml:11:13 | @@ -1151,13 +1156,14 @@ fn package_selection() { [WARNING] unused dependency --> foo/Cargo.toml:9:13 | -9 | unused = "0.1.0" - | ^^^^^^^^^^^^^^^^ +9 | unused_foo = "0.1.0" + | ^^^^^^^^^^^^^^^^^^^^ | [HELP] remove the dependency | -9 - unused = "0.1.0" +9 - unused_foo = "0.1.0" | +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), @@ -1168,18 +1174,18 @@ fn package_selection() { .masquerade_as_nightly_cargo(&["cargo-lints"]) .with_stderr_data( str![[r#" -[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s [WARNING] unused dependency --> bar/Cargo.toml:9:13 | -9 | unused = "0.1.0" - | ^^^^^^^^^^^^^^^^ +9 | unused_bar = "0.1.0" + | ^^^^^^^^^^^^^^^^^^^^ | = [NOTE] `cargo::unused_dependencies` is set to `warn` in `[lints]` [HELP] remove the dependency | -9 - unused = "0.1.0" +9 - unused_bar = "0.1.0" | +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]] .unordered(), @@ -1314,17 +1320,6 @@ pub fn fun() -> &'static str { [CHECKING] transitive v0.1.1 [CHECKING] intermediate v0.1.0 [CHECKING] foo v0.1.0 ([ROOT]/foo) -[WARNING] unused dependency - --> Cargo.toml:10:13 - | -10 | transitive = { version = "0.1.1", features = ["a"] } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = [NOTE] `cargo::unused_dependencies` is set to `warn` in `[lints]` -[HELP] remove the dependency - | -10 - transitive = { version = "0.1.1", features = ["a"] } - | [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]