Skip to content

Commit 03b05e1

Browse files
committed
fix: improve message for inactive weak optional feature with edition2024
1 parent a1cd23f commit 03b05e1

File tree

7 files changed

+115
-21
lines changed

7 files changed

+115
-21
lines changed

crates/cargo-util-schemas/src/manifest/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ pub struct TomlManifest {
5858
/// Note: this is populated by the caller, rather than automatically
5959
#[serde(skip)]
6060
pub _unused_keys: BTreeSet<String>,
61+
#[serde(skip)]
62+
pub _unused_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
63+
#[serde(skip)]
64+
pub _unused_dev_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
65+
#[serde(skip)]
66+
pub _unused_build_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
6167
}
6268

6369
impl TomlManifest {
@@ -1377,6 +1383,13 @@ pub struct TomlPlatform {
13771383
pub dev_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
13781384
#[serde(rename = "dev_dependencies")]
13791385
pub dev_dependencies2: Option<BTreeMap<PackageName, InheritableDependency>>,
1386+
1387+
#[serde(skip)]
1388+
pub _unused_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
1389+
#[serde(skip)]
1390+
pub _unused_dev_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
1391+
#[serde(skip)]
1392+
pub _unused_build_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
13801393
}
13811394

13821395
impl TomlPlatform {

crates/resolver-tests/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ pub fn resolve_with_global_context_raw(
164164
&BTreeMap::new(),
165165
None::<&String>,
166166
None::<RustVersion>,
167+
None,
167168
)
168169
.unwrap();
169170
let opts = ResolveOpts::everything();
@@ -483,6 +484,7 @@ pub fn pkg_dep<T: ToPkgId>(name: T, dep: Vec<Dependency>) -> Summary {
483484
&BTreeMap::new(),
484485
link,
485486
None::<RustVersion>,
487+
None,
486488
)
487489
.unwrap()
488490
}
@@ -511,6 +513,7 @@ pub fn pkg_loc(name: &str, loc: &str) -> Summary {
511513
&BTreeMap::new(),
512514
link,
513515
None::<RustVersion>,
516+
None,
514517
)
515518
.unwrap()
516519
}
@@ -525,6 +528,7 @@ pub fn remove_dep(sum: &Summary, ind: usize) -> Summary {
525528
&BTreeMap::new(),
526529
sum.links().map(|a| a.as_str()),
527530
None::<RustVersion>,
531+
None,
528532
)
529533
.unwrap()
530534
}

src/cargo/core/resolver/version_prefs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ mod test {
141141
&features,
142142
None::<&String>,
143143
msrv.map(|m| m.parse().unwrap()),
144+
None,
144145
)
145146
.unwrap()
146147
}

src/cargo/core/summary.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ impl Summary {
3838
features: &BTreeMap<InternedString, Vec<InternedString>>,
3939
links: Option<impl Into<InternedString>>,
4040
rust_version: Option<RustVersion>,
41+
unused_dependencies: Option<Vec<Dependency>>,
4142
) -> CargoResult<Summary> {
4243
// ****CAUTION**** If you change anything here that may raise a new
4344
// error, be sure to coordinate that change with either the index
@@ -51,7 +52,11 @@ impl Summary {
5152
)
5253
}
5354
}
54-
let feature_map = build_feature_map(features, &dependencies)?;
55+
let feature_map = build_feature_map(
56+
features,
57+
&dependencies,
58+
&unused_dependencies.unwrap_or_default(),
59+
)?;
5560
Ok(Summary {
5661
inner: Rc::new(Inner {
5762
package_id: pkg_id,
@@ -154,6 +159,7 @@ impl Hash for Summary {
154159
fn build_feature_map(
155160
features: &BTreeMap<InternedString, Vec<InternedString>>,
156161
dependencies: &[Dependency],
162+
unused_dependencies: &[Dependency],
157163
) -> CargoResult<FeatureMap> {
158164
use self::FeatureValue::*;
159165
let mut dep_map = HashMap::new();
@@ -163,6 +169,13 @@ fn build_feature_map(
163169
.or_insert_with(Vec::new)
164170
.push(dep);
165171
}
172+
let mut unused_dep_map = HashMap::new();
173+
for dep in unused_dependencies.iter() {
174+
unused_dep_map
175+
.entry(dep.name_in_toml())
176+
.or_insert_with(Vec::new)
177+
.push(dep);
178+
}
166179

167180
let mut map: FeatureMap = features
168181
.iter()
@@ -312,6 +325,12 @@ fn build_feature_map(
312325
)
313326
}
314327

328+
// editon2024 stops expose implicit features, which will strip weak optional dependencies from `dependencies`
329+
if *weak && unused_dep_map.get(dep_name).is_some() {
330+
bail!(
331+
"feature `{feature}` includes `{fv}`, but missing `dep:{dep_name}` to activate it, see the `dep:` syntax in https://doc.rust-lang.org/stable/cargo/reference/features.html#optional-dependencies"
332+
);
333+
}
315334
// Validation of the feature name will be performed in the resolver.
316335
if !is_any_dep {
317336
bail!(

src/cargo/sources/registry/index/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ impl IndexSummary {
736736
features.entry(name).or_default().extend(values);
737737
}
738738
}
739-
let mut summary = Summary::new(pkgid, deps, &features, links, rust_version)?;
739+
let mut summary = Summary::new(pkgid, deps, &features, links, rust_version, None)?;
740740
summary.set_checksum(cksum);
741741

742742
let v_max = if bindeps {

src/cargo/util/toml/mod.rs

+75-18
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ fn resolve_toml(
296296
badges: None,
297297
lints: None,
298298
_unused_keys: Default::default(),
299+
_unused_dependencies: Default::default(),
300+
_unused_dev_dependencies: Default::default(),
301+
_unused_build_dependencies: Default::default(),
299302
};
300303

301304
let package_root = manifest_file.parent().unwrap();
@@ -375,7 +378,10 @@ fn resolve_toml(
375378
})
376379
.unwrap_or_default();
377380

378-
resolved_toml.dependencies = resolve_dependencies(
381+
(
382+
resolved_toml.dependencies,
383+
resolved_toml._unused_dependencies,
384+
) = resolve_dependencies(
379385
gctx,
380386
edition,
381387
&features,
@@ -395,7 +401,10 @@ fn resolve_toml(
395401
edition,
396402
warnings,
397403
)?;
398-
resolved_toml.dev_dependencies = resolve_dependencies(
404+
(
405+
resolved_toml.dev_dependencies,
406+
resolved_toml._unused_dev_dependencies,
407+
) = resolve_dependencies(
399408
gctx,
400409
edition,
401410
&features,
@@ -415,7 +424,10 @@ fn resolve_toml(
415424
edition,
416425
warnings,
417426
)?;
418-
resolved_toml.build_dependencies = resolve_dependencies(
427+
(
428+
resolved_toml.build_dependencies,
429+
resolved_toml._unused_build_dependencies,
430+
) = resolve_dependencies(
419431
gctx,
420432
edition,
421433
&features,
@@ -428,7 +440,7 @@ fn resolve_toml(
428440
)?;
429441
let mut resolved_target = BTreeMap::new();
430442
for (name, platform) in original_toml.target.iter().flatten() {
431-
let resolved_dependencies = resolve_dependencies(
443+
let (resolved_dependencies, _unused_dependencies) = resolve_dependencies(
432444
gctx,
433445
edition,
434446
&features,
@@ -448,7 +460,7 @@ fn resolve_toml(
448460
edition,
449461
warnings,
450462
)?;
451-
let resolved_dev_dependencies = resolve_dependencies(
463+
let (resolved_dev_dependencies, _unused_dev_dependencies) = resolve_dependencies(
452464
gctx,
453465
edition,
454466
&features,
@@ -468,7 +480,7 @@ fn resolve_toml(
468480
edition,
469481
warnings,
470482
)?;
471-
let resolved_build_dependencies = resolve_dependencies(
483+
let (resolved_build_dependencies, _unused_build_dependencies) = resolve_dependencies(
472484
gctx,
473485
edition,
474486
&features,
@@ -487,6 +499,9 @@ fn resolve_toml(
487499
build_dependencies2: None,
488500
dev_dependencies: resolved_dev_dependencies,
489501
dev_dependencies2: None,
502+
_unused_dependencies,
503+
_unused_dev_dependencies,
504+
_unused_build_dependencies,
490505
},
491506
);
492507
}
@@ -694,12 +709,16 @@ fn resolve_dependencies<'a>(
694709
inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>,
695710
package_root: &Path,
696711
warnings: &mut Vec<String>,
697-
) -> CargoResult<Option<BTreeMap<manifest::PackageName, manifest::InheritableDependency>>> {
712+
) -> CargoResult<(
713+
Option<BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
714+
Option<BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
715+
)> {
698716
let Some(dependencies) = orig_deps else {
699-
return Ok(None);
717+
return Ok((None, None));
700718
};
701719

702720
let mut deps = BTreeMap::new();
721+
let mut ununsed_deps = BTreeMap::new();
703722
for (name_in_toml, v) in dependencies.iter() {
704723
let mut resolved = dependency_inherit_with(
705724
v.clone(),
@@ -763,9 +782,14 @@ fn resolve_dependencies<'a>(
763782
name_in_toml.clone(),
764783
manifest::InheritableDependency::Value(resolved.clone()),
765784
);
785+
} else {
786+
ununsed_deps.insert(
787+
name_in_toml.clone(),
788+
manifest::InheritableDependency::Value(resolved.clone()),
789+
);
766790
}
767791
}
768-
Ok(Some(deps))
792+
Ok((Some(deps), Some(ununsed_deps)))
769793
}
770794

771795
fn load_inheritable_fields(
@@ -1292,36 +1316,52 @@ fn to_real_manifest(
12921316

12931317
// Collect the dependencies.
12941318
let mut deps = Vec::new();
1319+
let mut unused_deps = Vec::new();
12951320
let mut manifest_ctx = ManifestContext {
12961321
deps: &mut deps,
12971322
source_id,
12981323
gctx,
12991324
warnings,
13001325
platform: None,
13011326
root: package_root,
1327+
unused_deps: &mut unused_deps,
13021328
};
1303-
gather_dependencies(&mut manifest_ctx, resolved_toml.dependencies.as_ref(), None)?;
1329+
gather_dependencies(
1330+
&mut manifest_ctx,
1331+
resolved_toml.dependencies.as_ref(),
1332+
resolved_toml._unused_dependencies.as_ref(),
1333+
None,
1334+
)?;
13041335
gather_dependencies(
13051336
&mut manifest_ctx,
13061337
resolved_toml.dev_dependencies(),
1338+
None,
13071339
Some(DepKind::Development),
13081340
)?;
13091341
gather_dependencies(
13101342
&mut manifest_ctx,
13111343
resolved_toml.build_dependencies(),
1344+
resolved_toml._unused_build_dependencies.as_ref(),
13121345
Some(DepKind::Build),
13131346
)?;
13141347
for (name, platform) in resolved_toml.target.iter().flatten() {
13151348
manifest_ctx.platform = Some(name.parse()?);
1316-
gather_dependencies(&mut manifest_ctx, platform.dependencies.as_ref(), None)?;
1349+
gather_dependencies(
1350+
&mut manifest_ctx,
1351+
platform.dependencies.as_ref(),
1352+
platform._unused_dependencies.as_ref(),
1353+
None,
1354+
)?;
13171355
gather_dependencies(
13181356
&mut manifest_ctx,
13191357
platform.build_dependencies(),
1358+
None,
13201359
Some(DepKind::Build),
13211360
)?;
13221361
gather_dependencies(
13231362
&mut manifest_ctx,
13241363
platform.dev_dependencies(),
1364+
None,
13251365
Some(DepKind::Development),
13261366
)?;
13271367
}
@@ -1452,6 +1492,7 @@ fn to_real_manifest(
14521492
.collect(),
14531493
resolved_package.links.as_deref(),
14541494
rust_version.clone(),
1495+
Some(unused_deps),
14551496
)?;
14561497
if summary.features().contains_key("default-features") {
14571498
warnings.push(
@@ -1582,6 +1623,7 @@ fn to_virtual_manifest(
15821623
warnings,
15831624
platform: None,
15841625
root,
1626+
unused_deps: &mut Vec::new(),
15851627
};
15861628
(
15871629
replace(&original_toml, &mut manifest_ctx)?,
@@ -1650,23 +1692,31 @@ struct ManifestContext<'a, 'b> {
16501692
warnings: &'a mut Vec<String>,
16511693
platform: Option<Platform>,
16521694
root: &'a Path,
1695+
unused_deps: &'a mut Vec<Dependency>,
16531696
}
16541697

16551698
#[tracing::instrument(skip_all)]
16561699
fn gather_dependencies(
16571700
manifest_ctx: &mut ManifestContext<'_, '_>,
16581701
resolved_deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
1702+
unuse_resolved_deps: Option<&BTreeMap<manifest::PackageName, manifest::InheritableDependency>>,
16591703
kind: Option<DepKind>,
16601704
) -> CargoResult<()> {
1661-
let Some(dependencies) = resolved_deps else {
1662-
return Ok(());
1705+
if let Some(dependencies) = resolved_deps {
1706+
for (n, v) in dependencies.iter() {
1707+
let resolved = v.resolved().expect("previously resolved");
1708+
let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind)?;
1709+
manifest_ctx.deps.push(dep);
1710+
}
1711+
};
1712+
if let Some(dependencies) = unuse_resolved_deps {
1713+
for (n, v) in dependencies.iter() {
1714+
let resolved = v.resolved().expect("previously resolved");
1715+
let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind)?;
1716+
manifest_ctx.unused_deps.push(dep);
1717+
}
16631718
};
16641719

1665-
for (n, v) in dependencies.iter() {
1666-
let resolved = v.resolved().expect("previously resolved");
1667-
let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind)?;
1668-
manifest_ctx.deps.push(dep);
1669-
}
16701720
Ok(())
16711721
}
16721722

@@ -1775,6 +1825,7 @@ pub(crate) fn to_dependency<P: ResolveToPath + Clone>(
17751825
warnings,
17761826
platform,
17771827
root,
1828+
unused_deps: &mut Vec::new(),
17781829
},
17791830
kind,
17801831
)
@@ -2642,6 +2693,9 @@ fn prepare_toml_for_publish(
26422693
dev_dependencies2: None,
26432694
build_dependencies: map_deps(gctx, v.build_dependencies(), all)?,
26442695
build_dependencies2: None,
2696+
_unused_dependencies: Default::default(),
2697+
_unused_dev_dependencies: Default::default(),
2698+
_unused_build_dependencies: Default::default(),
26452699
},
26462700
))
26472701
})
@@ -2658,6 +2712,9 @@ fn prepare_toml_for_publish(
26582712
cargo_features: me.cargo_features.clone(),
26592713
lints: me.lints.clone(),
26602714
_unused_keys: Default::default(),
2715+
_unused_dependencies: Default::default(),
2716+
_unused_dev_dependencies: Default::default(),
2717+
_unused_build_dependencies: Default::default(),
26612718
};
26622719
strip_features(&mut manifest);
26632720
return Ok(manifest);

tests/testsuite/lints/unused_optional_dependencies.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ Caused by:
296296
error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
297297
298298
Caused by:
299-
feature `foo_feature` includes `dep_name?/dep_feature`, but `dep_name` is not a dependency
299+
feature `foo_feature` includes `dep_name?/dep_feature`, but missing `dep:dep_name` to activate it, see the `dep:` syntax in [..]
300300
",
301301
)
302302
.run();

0 commit comments

Comments
 (0)