More helpful missing feature error message

This commit is contained in:
Kornel 2024-08-21 13:50:54 +01:00
parent a0acc29f7b
commit e8b28cf87e
No known key found for this signature in database
2 changed files with 97 additions and 23 deletions

View File

@ -1363,12 +1363,12 @@ impl<'gctx> Workspace<'gctx> {
} }
} }
fn report_unknown_features_error( fn missing_feature_spelling_suggestions(
&self, &self,
specs: &[PackageIdSpec], selected_members: &[&Package],
cli_features: &CliFeatures, cli_features: &CliFeatures,
found_features: &BTreeSet<FeatureValue>, found_features: &BTreeSet<FeatureValue>,
) -> CargoResult<()> { ) -> Vec<String> {
// Keeps track of which features were contained in summary of `member` to suggest similar features in errors // 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(); let mut summary_features: Vec<InternedString> = Default::default();
@ -1387,10 +1387,7 @@ impl<'gctx> Workspace<'gctx> {
let mut optional_dependency_names_per_member: BTreeMap<&Package, BTreeSet<InternedString>> = let mut optional_dependency_names_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
Default::default(); Default::default();
for member in self for &member in selected_members {
.members()
.filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
{
// Only include features this member defines. // Only include features this member defines.
let summary = member.summary(); let summary = member.summary();
@ -1428,7 +1425,7 @@ impl<'gctx> Workspace<'gctx> {
edit_distance(a.as_str(), b.as_str(), 3).is_some() edit_distance(a.as_str(), b.as_str(), 3).is_some()
}; };
let suggestions: Vec<_> = cli_features cli_features
.features .features
.difference(found_features) .difference(found_features)
.map(|feature| match feature { .map(|feature| match feature {
@ -1522,8 +1519,15 @@ impl<'gctx> Workspace<'gctx> {
}) })
.sorted() .sorted()
.take(5) .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 let unknown: Vec<_> = cli_features
.features .features
.difference(found_features) .difference(found_features)
@ -1531,20 +1535,72 @@ impl<'gctx> Workspace<'gctx> {
.sorted() .sorted()
.collect(); .collect();
if suggestions.is_empty() { let (selected_members, unselected_members): (Vec<_>, Vec<_>) = self
bail!( .members()
"none of the selected packages contains these features: {}", .partition(|member| specs.iter().any(|spec| spec.matches(member.package_id())));
unknown.join(", ")
); 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 { } else {
bail!( "these features"
"none of the selected packages contains these features: {}, did you mean: {}?", };
unknown.join(", "), let mut msg = if let [singular] = &selected_members[..] {
suggestions.join(", ") format!(
"the package '{}' does not contain {these_features}: {}",
singular.name(),
unknown.join(", ")
)
} else {
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: package{} with the missing feature{}: {}",
if missing_packages_with_the_features.len() != 1 {
"s"
} else {
""
},
if unknown.len() != 1 { "s" } else { "" },
missing_packages_with_the_features.join(", ")
)?;
} 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 /// New command-line feature selection behavior with resolver = "2" or the
/// root of a virtual workspace. See `allows_new_cli_feature_behavior`. /// root of a virtual workspace. See `allows_new_cli_feature_behavior`.
fn members_with_features_new( fn members_with_features_new(

View File

@ -75,7 +75,9 @@ fn virtual_no_default_features() {
p.cargo("check --features foo") p.cargo("check --features foo")
.with_status(101) .with_status(101)
.with_stderr_data(str![[r#" .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(); .run();
@ -83,7 +85,9 @@ fn virtual_no_default_features() {
p.cargo("check --features a/dep1,b/f1,b/f2,f2") p.cargo("check --features a/dep1,b/f1,b/f2,f2")
.with_status(101) .with_status(101)
.with_stderr_data(str![[r#" .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(); .run();
@ -91,7 +95,9 @@ fn virtual_no_default_features() {
p.cargo("check --features a/dep,b/f1,b/f2,f2") p.cargo("check --features a/dep,b/f1,b/f2,f2")
.with_status(101) .with_status(101)
.with_stderr_data(str![[r#" .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(); .run();
@ -99,7 +105,18 @@ fn virtual_no_default_features() {
p.cargo("check --features a/dep,a/dep1") p.cargo("check --features a/dep,a/dep1")
.with_status(101) .with_status(101)
.with_stderr_data(str![[r#" .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] package with the missing feature: a
"#]]) "#]])
.run(); .run();
@ -126,7 +143,8 @@ fn virtual_typo_member_feature() {
.cargo("check --features a/deny-warning") .cargo("check --features a/deny-warning")
.with_status(101) .with_status(101)
.with_stderr_data(str![[r#" .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(); .run();