Skip to content

Commit

Permalink
More helpful missing feature error message
Browse files Browse the repository at this point in the history
  • Loading branch information
kornelski committed Aug 21, 2024
1 parent 9e152bb commit ce83c84
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 22 deletions.
92 changes: 75 additions & 17 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1363,12 +1363,12 @@ impl<'gctx> Workspace<'gctx> {
}
}

fn report_unknown_features_error(
fn missing_feature_spelling_suggestions(
&self,
specs: &[PackageIdSpec],
selected_members: &[&Package],
cli_features: &CliFeatures,
found_features: &BTreeSet<FeatureValue>,
) -> CargoResult<()> {
) -> Vec<String> {
// Keeps track of which features were contained in summary of `member` to suggest similar features in errors
let mut summary_features: Vec<InternedString> = Default::default();

Expand All @@ -1387,10 +1387,7 @@ impl<'gctx> Workspace<'gctx> {
let mut optional_dependency_names_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
Default::default();

for member in self
.members()
.filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
{
for &member in selected_members {
// Only include features this member defines.
let summary = member.summary();

Expand Down Expand Up @@ -1428,7 +1425,7 @@ impl<'gctx> Workspace<'gctx> {
edit_distance(a.as_str(), b.as_str(), 3).is_some()
};

let suggestions: Vec<_> = cli_features
cli_features
.features
.difference(found_features)
.map(|feature| match feature {
Expand Down Expand Up @@ -1522,27 +1519,88 @@ impl<'gctx> Workspace<'gctx> {
})
.sorted()
.take(5)
.collect();
.collect()
}

fn report_unknown_features_error(
&self,
specs: &[PackageIdSpec],
cli_features: &CliFeatures,
found_features: &BTreeSet<FeatureValue>,
) -> CargoResult<()> {
let unknown: Vec<_> = cli_features
.features
.difference(found_features)
.map(|feature| feature.to_string())
.sorted()
.collect();

if suggestions.is_empty() {
bail!(
"none of the selected packages contains these features: {}",
let (selected_members, unselected_members): (Vec<_>, Vec<_>) = self
.members()
.partition(|member| specs.iter().any(|spec| spec.matches(member.package_id())));

let missing_packages_with_the_features = unselected_members
.into_iter()
.filter(|member| {
unknown
.iter()
.any(|feature| member.summary().features().contains_key(&**feature))
})
.map(|m| m.name())
.collect_vec();

let these_features = if unknown.len() == 1 {
"this feature"
} else {
"these features"
};
let mut msg = if let [singular] = &selected_members[..] {
format!(
"the package '{}' does not contain {these_features}: {}",
singular.name(),
unknown.join(", ")
);
)
} else {
bail!(
"none of the selected packages contains these features: {}, did you mean: {}?",
unknown.join(", "),
suggestions.join(", ")
let names = selected_members.iter().map(|m| m.name()).join(", ");
format!("none of the selected packages contains {these_features}: {}\nselected packages: {names}", unknown.join(", "))
};

use std::fmt::Write;
if !missing_packages_with_the_features.is_empty() {
write!(
&mut msg,
"\nhelp: include package{} with the missing feature{}:",
if missing_packages_with_the_features.len() != 1 {
"s"
} else {
""
},
if unknown.len() != 1 { "s" } else { "" }
)?;
for member in &missing_packages_with_the_features {
write!(&mut msg, " --package {member}")?;
}
} else {
let suggestions = self.missing_feature_spelling_suggestions(
&selected_members,
cli_features,
found_features,
);
if !suggestions.is_empty() {
write!(
&mut msg,
"\nhelp: there {}: {}",
if suggestions.len() == 1 {
"is a similarly named feature"
} else {
"are similarly named features"
},
suggestions.join(", ")
)?;
}
}

bail!("{msg}")
}

/// New command-line feature selection behavior with resolver = "2" or the
Expand Down
28 changes: 23 additions & 5 deletions tests/testsuite/package_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,31 +75,48 @@ fn virtual_no_default_features() {
p.cargo("check --features foo")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] none of the selected packages contains these features: foo, did you mean: f1?
[ERROR] none of the selected packages contains this feature: foo
selected packages: a, b
[HELP] there is a similarly named feature: f1
"#]])
.run();

p.cargo("check --features a/dep1,b/f1,b/f2,f2")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] none of the selected packages contains these features: b/f2, f2, did you mean: f1?
[ERROR] none of the selected packages contains these features: b/f2, f2
selected packages: a, b
[HELP] there is a similarly named feature: f1
"#]])
.run();

p.cargo("check --features a/dep,b/f1,b/f2,f2")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] none of the selected packages contains these features: a/dep, b/f2, f2, did you mean: a/dep1, f1?
[ERROR] none of the selected packages contains these features: a/dep, b/f2, f2
selected packages: a, b
[HELP] there are similarly named features: a/dep1, f1
"#]])
.run();

p.cargo("check --features a/dep,a/dep1")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] none of the selected packages contains these features: a/dep, did you mean: b/f1?
[ERROR] none of the selected packages contains this feature: a/dep
selected packages: a, b
[HELP] there is a similarly named feature: b/f1
"#]])
.run();

p.cargo("check -p b --features=dep1")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] the package 'b' does not contain this feature: dep1
[HELP] include package with the missing feature: --package a
"#]])
.run();
Expand All @@ -126,7 +143,8 @@ fn virtual_typo_member_feature() {
.cargo("check --features a/deny-warning")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] none of the selected packages contains these features: a/deny-warning, did you mean: a/deny-warnings?
[ERROR] the package 'a' does not contain this feature: a/deny-warning
[HELP] there is a similarly named feature: a/deny-warnings
"#]])
.run();
Expand Down

0 comments on commit ce83c84

Please sign in to comment.