diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs index f0b56db8a..2a93ced27 100644 --- a/src/cargo/core/resolver/dep_cache.rs +++ b/src/cargo/core/resolver/dep_cache.rs @@ -524,15 +524,13 @@ impl RequirementError { "feature", ); ActivateError::Fatal(anyhow::format_err!( - "Package `{}` does not have the feature `{}`{}", + "package `{}` does not have the feature `{}`{}", summary.package_id(), feat, closest )) } - Some(p) => { - ActivateError::Conflict(p, ConflictReason::MissingFeatures(feat)) - } + Some(p) => ActivateError::Conflict(p, ConflictReason::MissingFeature(feat)), }; } if deps.iter().any(|dep| dep.is_optional()) { @@ -555,9 +553,11 @@ impl RequirementError { } ActivateError::Fatal(anyhow::format_err!( "\ -Package `{}` does not have feature `{}`. It has an optional dependency \ -with that name, but that dependency uses the \"dep:\" \ -syntax in the features table, so it does not have an implicit feature with that name.{}", +package `{}` does not have feature `{}` + +help: an optional dependency \ +with that name exists, but the `features` table includes it with the \"dep:\" \ +syntax so it does not have an implicit feature with that name{}", summary.package_id(), feat, suggestion @@ -571,8 +571,9 @@ syntax in the features table, so it does not have an implicit feature with that } else { match parent { None => ActivateError::Fatal(anyhow::format_err!( - "Package `{}` does not have feature `{}`. It has a required dependency \ - with that name, but only optional dependencies can be used as features.", + "package `{}` does not have feature `{}` + +help: a depednency with that name exists but it is required dependency and only optional dependencies can be used as features.", summary.package_id(), feat, )), @@ -592,9 +593,7 @@ syntax in the features table, so it does not have an implicit feature with that )), // This code path currently isn't used, since `foo/bar` // and `dep:` syntax is not allowed in a dependency. - Some(p) => { - ActivateError::Conflict(p, ConflictReason::MissingFeatures(dep_name)) - } + Some(p) => ActivateError::Conflict(p, ConflictReason::MissingFeature(dep_name)), } } RequirementError::Cycle(feat) => ActivateError::Fatal(anyhow::format_err!( diff --git a/src/cargo/core/resolver/errors.rs b/src/cargo/core/resolver/errors.rs index 6c8679ed1..169fcd1fa 100644 --- a/src/cargo/core/resolver/errors.rs +++ b/src/cargo/core/resolver/errors.rs @@ -5,7 +5,7 @@ use std::task::Poll; use crate::core::{Dependency, PackageId, Registry, Summary}; use crate::sources::source::QueryKind; use crate::sources::IndexSummary; -use crate::util::edit_distance::edit_distance; +use crate::util::edit_distance::{closest, edit_distance}; use crate::util::errors::CargoResult; use crate::util::{GlobalContext, OptVersionReq, VersionExt}; use anyhow::Error; @@ -137,7 +137,7 @@ pub(super) fn activation_error( has_semver = true; } ConflictReason::Links(link) => { - msg.push_str("\n\nthe package `"); + msg.push_str("\n\npackage `"); msg.push_str(&*dep.package_name()); msg.push_str("` links to the native library `"); msg.push_str(link); @@ -150,46 +150,54 @@ pub(super) fn activation_error( msg.push_str(link); msg.push_str("\"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links."); } - ConflictReason::MissingFeatures(features) => { - msg.push_str("\n\nthe package `"); + ConflictReason::MissingFeature(feature) => { + msg.push_str("\n\npackage `"); msg.push_str(&*p.name()); msg.push_str("` depends on `"); msg.push_str(&*dep.package_name()); - msg.push_str("`, with features: `"); - msg.push_str(features); + msg.push_str("` with feature `"); + msg.push_str(feature); msg.push_str("` but `"); msg.push_str(&*dep.package_name()); - msg.push_str("` does not have these features.\n"); + msg.push_str("` does not have that feature.\n"); + let latest = candidates.last().expect("in the non-empty branch"); + if let Some(closest) = closest(feature, latest.features().keys(), |k| k) { + msg.push_str(" package `"); + msg.push_str(&*dep.package_name()); + msg.push_str("` does have feature `"); + msg.push_str(closest); + msg.push_str("`\n"); + } // p == parent so the full path is redundant. } - ConflictReason::RequiredDependencyAsFeature(features) => { - msg.push_str("\n\nthe package `"); + ConflictReason::RequiredDependencyAsFeature(feature) => { + msg.push_str("\n\npackage `"); msg.push_str(&*p.name()); msg.push_str("` depends on `"); msg.push_str(&*dep.package_name()); - msg.push_str("`, with features: `"); - msg.push_str(features); + msg.push_str("` with feature `"); + msg.push_str(feature); msg.push_str("` but `"); msg.push_str(&*dep.package_name()); - msg.push_str("` does not have these features.\n"); + msg.push_str("` does not have that feature.\n"); msg.push_str( - " It has a required dependency with that name, \ + " A required dependency with that name exists, \ but only optional dependencies can be used as features.\n", ); // p == parent so the full path is redundant. } - ConflictReason::NonImplicitDependencyAsFeature(features) => { - msg.push_str("\n\nthe package `"); + ConflictReason::NonImplicitDependencyAsFeature(feature) => { + msg.push_str("\n\npackage `"); msg.push_str(&*p.name()); msg.push_str("` depends on `"); msg.push_str(&*dep.package_name()); - msg.push_str("`, with features: `"); - msg.push_str(features); + msg.push_str("` with feature `"); + msg.push_str(feature); msg.push_str("` but `"); msg.push_str(&*dep.package_name()); - msg.push_str("` does not have these features.\n"); + msg.push_str("` does not have that feature.\n"); msg.push_str( - " It has an optional dependency with that name, \ + " An optional dependency with that name exists, \ but that dependency uses the \"dep:\" \ syntax in the features table, so it does not have an \ implicit feature with that name.\n", diff --git a/src/cargo/core/resolver/types.rs b/src/cargo/core/resolver/types.rs index 2a5c51af5..8bc1c9faa 100644 --- a/src/cargo/core/resolver/types.rs +++ b/src/cargo/core/resolver/types.rs @@ -339,10 +339,10 @@ pub enum ConflictReason { /// we're only allowed one per dependency graph. Links(InternedString), - /// A dependency listed features that weren't actually available on the + /// A dependency listed a feature that wasn't actually available on the /// candidate. For example we tried to activate feature `foo` but the /// candidate we're activating didn't actually have the feature `foo`. - MissingFeatures(InternedString), + MissingFeature(InternedString), /// A dependency listed a feature that ended up being a required dependency. /// For example we tried to activate feature `foo` but the @@ -360,8 +360,8 @@ impl ConflictReason { matches!(self, ConflictReason::Links(_)) } - pub fn is_missing_features(&self) -> bool { - matches!(self, ConflictReason::MissingFeatures(_)) + pub fn is_missing_feature(&self) -> bool { + matches!(self, ConflictReason::MissingFeature(_)) } pub fn is_required_dependency_as_features(&self) -> bool { diff --git a/src/cargo/core/summary.rs b/src/cargo/core/summary.rs index 168a20fc6..d12a6f49b 100644 --- a/src/cargo/core/summary.rs +++ b/src/cargo/core/summary.rs @@ -1,4 +1,5 @@ use crate::core::{Dependency, PackageId, SourceId}; +use crate::util::closest_msg; use crate::util::interning::InternedString; use crate::util::CargoResult; use anyhow::bail; @@ -241,9 +242,10 @@ fn build_feature_map( Feature(f) => { if !features.contains_key(f) { if !is_any_dep { + let closest = closest_msg(f, features.keys(), |k| k, "feature"); bail!( "feature `{feature}` includes `{fv}` which is neither a dependency \ - nor another feature" + nor another feature{closest}" ); } if is_optional_dep { diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs index 17d8228bb..f3ed2727c 100644 --- a/tests/testsuite/build_script.rs +++ b/tests/testsuite/build_script.rs @@ -1032,7 +1032,7 @@ fn links_duplicates() { ... required by package `foo v0.5.0 ([ROOT]/foo)` versions that meet the requirements `*` are: 0.5.0 -the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: +package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: package `foo v0.5.0 ([ROOT]/foo)` Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "a"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. @@ -1159,7 +1159,7 @@ fn links_duplicates_deep_dependency() { ... which satisfies path dependency `a` of package `foo v0.5.0 ([ROOT]/foo)` versions that meet the requirements `*` are: 0.5.0 -the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: +package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: package `foo v0.5.0 ([ROOT]/foo)` Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "a"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. @@ -4767,7 +4767,7 @@ fn links_duplicates_with_cycle() { ... required by package `foo v0.5.0 ([ROOT]/foo)` versions that meet the requirements `*` are: 0.5.0 -the package `a` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: +package `a` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: package `foo v0.5.0 ([ROOT]/foo)` Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "a"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. diff --git a/tests/testsuite/features.rs b/tests/testsuite/features.rs index 94a69cc6e..da39e16bc 100644 --- a/tests/testsuite/features.rs +++ b/tests/testsuite/features.rs @@ -6,7 +6,7 @@ use cargo_test_support::str; use cargo_test_support::{basic_manifest, project}; #[cargo_test] -fn invalid1() { +fn feature_activates_missing_feature() { let p = project() .file( "Cargo.toml", @@ -32,6 +32,42 @@ fn invalid1() { Caused by: feature `bar` includes `baz` which is neither a dependency nor another feature + [HELP] a feature with a similar name exists: `bar` + +"#]]) + .run(); +} + +#[cargo_test] +fn feature_activates_typoed_feature() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + authors = [] + + [features] + bar = ["baz"] + jaz = [] + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + feature `bar` includes `baz` which is neither a dependency nor another feature + + [HELP] a feature with a similar name exists: `bar` + "#]]) .run(); } @@ -120,7 +156,7 @@ foo v0.0.1 ([ROOT]/foo) [bar,baz] } #[cargo_test] -fn invalid3() { +fn feature_activates_required_dependency() { let p = project() .file( "Cargo.toml", @@ -155,7 +191,7 @@ Caused by: } #[cargo_test] -fn invalid4() { +fn dependency_activates_missing_feature() { let p = project() .file( "Cargo.toml", @@ -183,7 +219,7 @@ fn invalid4() { ... required by package `foo v0.0.1 ([ROOT]/foo)` versions that meet the requirements `*` are: 0.0.1 -the package `foo` depends on `bar`, with features: `bar` but `bar` does not have these features. +package `foo` depends on `bar` with feature `bar` but `bar` does not have that feature. failed to select a version for `bar` which could resolve this conflict @@ -196,14 +232,65 @@ failed to select a version for `bar` which could resolve this conflict p.cargo("check --features test") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] Package `foo v0.0.1 ([ROOT]/foo)` does not have the feature `test` +[ERROR] package `foo v0.0.1 ([ROOT]/foo)` does not have the feature `test` "#]]) .run(); } #[cargo_test] -fn invalid5() { +fn dependency_activates_typoed_feature() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + authors = [] + + [dependencies.bar] + path = "bar" + features = ["bar"] + "#, + ) + .file("src/main.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + edition = "2015" + authors = [] + + [features] + baz = [] +"#, + ) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] failed to select a version for `bar`. + ... required by package `foo v0.0.1 ([ROOT]/foo)` +versions that meet the requirements `*` are: 0.0.1 + +package `foo` depends on `bar` with feature `bar` but `bar` does not have that feature. + package `bar` does have feature `baz` + + +failed to select a version for `bar` which could resolve this conflict + +"#]]) + .run(); +} + +#[cargo_test] +fn optional_dev_dependency() { let p = project() .file( "Cargo.toml", @@ -235,7 +322,7 @@ Caused by: } #[cargo_test] -fn invalid6() { +fn feature_activates_missing_dep_feature() { let p = project() .file( "Cargo.toml", @@ -269,7 +356,7 @@ fn invalid6() { } #[cargo_test] -fn invalid7() { +fn feature_activates_feature_inside_feature() { let p = project() .file( "Cargo.toml", @@ -304,7 +391,7 @@ fn invalid7() { } #[cargo_test] -fn invalid8() { +fn dependency_activates_dep_feature() { let p = project() .file( "Cargo.toml", @@ -339,7 +426,7 @@ Caused by: } #[cargo_test] -fn invalid9() { +fn cli_activates_required_dependency() { let p = project() .file( "Cargo.toml", @@ -362,7 +449,9 @@ fn invalid9() { p.cargo("check --features bar") .with_stderr_data(str![[r#" [LOCKING] 1 package to latest compatible version -[ERROR] Package `foo v0.0.1 ([ROOT]/foo)` does not have feature `bar`. It has a required dependency with that name, but only optional dependencies can be used as features. +[ERROR] package `foo v0.0.1 ([ROOT]/foo)` does not have feature `bar` + +[HELP] a depednency with that name exists but it is required dependency and only optional dependencies can be used as features. "#]]) .with_status(101) @@ -370,7 +459,7 @@ fn invalid9() { } #[cargo_test] -fn invalid10() { +fn dependency_activates_required_dependency() { let p = project() .file( "Cargo.toml", @@ -411,8 +500,8 @@ fn invalid10() { ... required by package `foo v0.0.1 ([ROOT]/foo)` versions that meet the requirements `*` are: 0.0.1 -the package `foo` depends on `bar`, with features: `baz` but `bar` does not have these features. - It has a required dependency with that name, but only optional dependencies can be used as features. +package `foo` depends on `bar` with feature `baz` but `bar` does not have that feature. + A required dependency with that name exists, but only optional dependencies can be used as features. failed to select a version for `bar` which could resolve this conflict diff --git a/tests/testsuite/features_namespaced.rs b/tests/testsuite/features_namespaced.rs index f7315feb8..f1048259f 100644 --- a/tests/testsuite/features_namespaced.rs +++ b/tests/testsuite/features_namespaced.rs @@ -75,6 +75,8 @@ fn namespaced_invalid_feature() { Caused by: feature `bar` includes `baz` which is neither a dependency nor another feature + [HELP] a feature with a similar name exists: `bar` + "#]]) .run(); } @@ -417,7 +419,9 @@ regex p.cargo("run --features lazy_static") .with_stderr_data(str![[r#" -[ERROR] Package `foo v0.1.0 ([ROOT]/foo)` does not have feature `lazy_static`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name. +[ERROR] package `foo v0.1.0 ([ROOT]/foo)` does not have feature `lazy_static` + +[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name Dependency `lazy_static` would be enabled by these features: - `regex` diff --git a/tests/testsuite/package_features.rs b/tests/testsuite/package_features.rs index 2aeb3903e..23a7402d2 100644 --- a/tests/testsuite/package_features.rs +++ b/tests/testsuite/package_features.rs @@ -338,7 +338,7 @@ f3f4 p.cargo("run -p bar --features f1,f2") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] Package `foo v0.1.0 ([ROOT]/foo)` does not have the feature `f2` +[ERROR] package `foo v0.1.0 ([ROOT]/foo)` does not have the feature `f2` [HELP] a feature with a similar name exists: `f1` @@ -406,7 +406,7 @@ fn feature_default_resolver() { p.cargo("check --features testt") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have the feature `testt` +[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have the feature `testt` [HELP] a feature with a similar name exists: `test` @@ -458,7 +458,9 @@ fn command_line_optional_dep() { .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version -[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name. +[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar` + +[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name Dependency `bar` would be enabled by these features: - `foo` @@ -496,7 +498,9 @@ fn command_line_optional_dep_three_options() { .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version -[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name. +[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar` + +[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name Dependency `bar` would be enabled by these features: - `f1` - `f2` @@ -537,7 +541,9 @@ fn command_line_optional_dep_many_options() { .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version -[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name. +[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar` + +[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name Dependency `bar` would be enabled by these features: - `f1` - `f2` @@ -583,7 +589,9 @@ fn command_line_optional_dep_many_paths() { .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version -[ERROR] Package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar`. It has an optional dependency with that name, but that dependency uses the "dep:" syntax in the features table, so it does not have an implicit feature with that name. +[ERROR] package `a v0.1.0 ([ROOT]/foo)` does not have feature `bar` + +[HELP] an optional dependency with that name exists, but the `features` table includes it with the "dep:" syntax so it does not have an implicit feature with that name Dependency `bar` would be enabled by these features: - `f1` - `f2` @@ -820,7 +828,7 @@ m1-feature set .cwd("member2") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] Package `member1 v0.1.0 ([ROOT]/foo/member1)` does not have the feature `m2-feature` +[ERROR] package `member1 v0.1.0 ([ROOT]/foo/member1)` does not have the feature `m2-feature` [HELP] a feature with a similar name exists: `m1-feature` diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index 13eeac122..711fde2e9 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -4007,7 +4007,7 @@ fn cyclical_dep_with_missing_feature() { ... required by package `foo v0.1.0 ([ROOT]/foo)` versions that meet the requirements `*` are: 0.1.0 -the package `foo` depends on `foo`, with features: `missing` but `foo` does not have these features. +package `foo` depends on `foo` with feature `missing` but `foo` does not have that feature. failed to select a version for `foo` which could resolve this conflict