From 428646d1a954490baab1fe07f90b8b45dcb6a9d3 Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Sun, 21 Jun 2026 23:20:33 +0200 Subject: [PATCH 1/7] feat(toml): allow overriding inherited default-features in 2024 Impl RFC 3945, on Edition 2024+ allow workspace members to override the workspace dependency's `default-features` setting. Earlier editions are unchanged: setting `default-features` on an inherited dependency is still ignored with a warning. Signed-off-by: 0xPoe --- src/workspace/parser/mod.rs | 56 +++++++++---------- .../testsuite/inheritable_workspace_fields.rs | 34 ++++++----- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/src/workspace/parser/mod.rs b/src/workspace/parser/mod.rs index 4098ef06dd0..d96f4ae0965 100644 --- a/src/workspace/parser/mod.rs +++ b/src/workspace/parser/mod.rs @@ -1216,25 +1216,31 @@ fn inner_dependency_inherit_with<'a>( } = &pkg_dep; let default_features = default_features.or(*default_features2); - match (default_features, merged_dep.default_features()) { - // member: default-features = true and - // workspace: default-features = false should turn on - // default-features - (Some(true), Some(false)) => { - merged_dep.default_features = Some(true); - } - // member: default-features = false and - // workspace: default-features = true should ignore member - // default-features - (Some(false), Some(true)) => { - deprecated_ws_default_features(name, Some(true), edition, warnings)?; - } - // member: default-features = false and - // workspace: dep = "1.0" should ignore member default-features - (Some(false), None) => { - deprecated_ws_default_features(name, None, edition, warnings)?; + // RFC 3945: Allow workspace members to override the workspace dependency's + // `default-features` setting. + if edition >= Edition::Edition2024 { + merged_dep.default_features = default_features.or(merged_dep.default_features); + } else { + match (default_features, merged_dep.default_features()) { + // member: default-features = true and + // workspace: default-features = false should turn on + // default-features + (Some(true), Some(false)) => { + merged_dep.default_features = Some(true); + } + // member: default-features = false and + // workspace: default-features = true should ignore member + // default-features + (Some(false), Some(true)) => { + deprecated_ws_default_features(name, Some(true), warnings); + } + // member: default-features = false and + // workspace: dep = "1.0" should ignore member default-features + (Some(false), None) => { + deprecated_ws_default_features(name, None, warnings); + } + _ => {} } - _ => {} } merged_dep.features = match (merged_dep.features.clone(), features.clone()) { (Some(dep_feat), Some(inherit_feat)) => Some( @@ -1255,24 +1261,18 @@ fn inner_dependency_inherit_with<'a>( fn deprecated_ws_default_features( label: &str, ws_def_feat: Option, - edition: Edition, warnings: &mut Vec, -) -> CargoResult<()> { +) { let ws_def_feat = match ws_def_feat { Some(true) => "true", Some(false) => "false", None => "not specified", }; - if Edition::Edition2024 <= edition { - anyhow::bail!("`default-features = false` cannot override workspace's `default-features`"); - } else { - warnings.push(format!( - "`default-features` is ignored for {label}, since `default-features` was \ + warnings.push(format!( + "`default-features` is ignored for {label}, since `default-features` was \ {ws_def_feat} for `workspace.dependencies.{label}`, \ this could become a hard error in the future" - )); - } - Ok(()) + )); } #[tracing::instrument(skip_all)] diff --git a/tests/testsuite/inheritable_workspace_fields.rs b/tests/testsuite/inheritable_workspace_fields.rs index 4f0ce8b04b3..0584ce280c7 100644 --- a/tests/testsuite/inheritable_workspace_fields.rs +++ b/tests/testsuite/inheritable_workspace_fields.rs @@ -1555,7 +1555,7 @@ fn warn_inherit_def_feat_true_member_def_feat_false() { } #[cargo_test] -fn warn_inherit_def_feat_true_member_def_feat_false_2024_edition() { +fn inherit_def_feat_true_member_def_feat_false_2024_edition() { Package::new("dep", "0.1.0") .feature("default", &["fancy_dep"]) .add_dep(Dependency::new("fancy_dep", "0.2").optional(true)) @@ -1586,15 +1586,14 @@ fn warn_inherit_def_feat_true_member_def_feat_false_2024_edition() { .build(); p.cargo("check") - .with_status(101) .with_stderr_data(str![[r#" -[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` - -Caused by: - error inheriting `dep` from workspace root manifest's `workspace.dependencies.dep` - -Caused by: - `default-features = false` cannot override workspace's `default-features` +[UPDATING] `dummy-registry` index +[LOCKING] 1 package to latest Rust [..] compatible version +[DOWNLOADING] crates ... +[DOWNLOADED] dep v0.1.0 (registry `dummy-registry`) +[CHECKING] dep v0.1.0 +[CHECKING] bar v0.2.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); @@ -1648,7 +1647,7 @@ fn warn_inherit_simple_member_def_feat_false() { } #[cargo_test] -fn warn_inherit_simple_member_def_feat_false_2024_edition() { +fn inherit_simple_member_def_feat_false_2024_edition() { Package::new("dep", "0.1.0") .feature("default", &["fancy_dep"]) .add_dep(Dependency::new("fancy_dep", "0.2").optional(true)) @@ -1679,15 +1678,14 @@ fn warn_inherit_simple_member_def_feat_false_2024_edition() { .build(); p.cargo("check") - .with_status(101) .with_stderr_data(str![[r#" -[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` - -Caused by: - error inheriting `dep` from workspace root manifest's `workspace.dependencies.dep` - -Caused by: - `default-features = false` cannot override workspace's `default-features` +[UPDATING] `dummy-registry` index +[LOCKING] 1 package to latest Rust [..] compatible version +[DOWNLOADING] crates ... +[DOWNLOADED] dep v0.1.0 (registry `dummy-registry`) +[CHECKING] dep v0.1.0 +[CHECKING] bar v0.2.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); From ef7de4d28deb68200f97a0a0d07f9afb85f350ad Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Sun, 21 Jun 2026 23:59:50 +0200 Subject: [PATCH 2/7] test(add): add inherit no default features case Signed-off-by: 0xPoe --- .../in/Cargo.toml | 6 +++++ .../in/bar/Cargo.toml | 6 +++++ .../in/bar/src/lib.rs | 0 .../in/foo/Cargo.toml | 4 +++ .../in/foo/src/lib.rs | 0 .../mod.rs | 25 +++++++++++++++++ .../out/Cargo.toml | 6 +++++ .../out/bar/Cargo.toml | 6 +++++ .../out/bar/src/lib.rs | 0 .../out/foo/Cargo.toml | 4 +++ .../out/foo/src/lib.rs | 0 .../stderr.term.svg | 27 +++++++++++++++++++ tests/testsuite/cargo_add/mod.rs | 1 + 13 files changed, 85 insertions(+) create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/Cargo.toml create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/bar/Cargo.toml create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/bar/src/lib.rs create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/foo/Cargo.toml create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/foo/src/lib.rs create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/mod.rs create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/Cargo.toml create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/Cargo.toml create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/src/lib.rs create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/foo/Cargo.toml create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/foo/src/lib.rs create mode 100644 tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/stderr.term.svg diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/Cargo.toml b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/Cargo.toml new file mode 100644 index 00000000000..84ae7d26601 --- /dev/null +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = ["foo", "bar"] +resolver = "3" + +[workspace.dependencies] +foo = { version = "0.0.0", path = "./foo", default-features = true } diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/bar/Cargo.toml b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/bar/Cargo.toml new file mode 100644 index 00000000000..3bd66f7b4a1 --- /dev/null +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/bar/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "bar" +version = "0.0.0" +edition = "2024" + +[dependencies] diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/bar/src/lib.rs b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/bar/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/foo/Cargo.toml b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/foo/Cargo.toml new file mode 100644 index 00000000000..c0a92ea13b9 --- /dev/null +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/foo/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "foo" +version = "0.0.0" +edition = "2024" diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/foo/src/lib.rs b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/in/foo/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/mod.rs b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/mod.rs new file mode 100644 index 00000000000..7c2925dd4ec --- /dev/null +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/mod.rs @@ -0,0 +1,25 @@ +use crate::prelude::*; +use cargo_test_support::Project; +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::str; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("add") + .args(["foo", "--no-default-features", "-p", "bar"]) + .current_dir(cwd) + .assert() + .failure() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/Cargo.toml b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/Cargo.toml new file mode 100644 index 00000000000..84ae7d26601 --- /dev/null +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = ["foo", "bar"] +resolver = "3" + +[workspace.dependencies] +foo = { version = "0.0.0", path = "./foo", default-features = true } diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/Cargo.toml b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/Cargo.toml new file mode 100644 index 00000000000..3bd66f7b4a1 --- /dev/null +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "bar" +version = "0.0.0" +edition = "2024" + +[dependencies] diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/src/lib.rs b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/foo/Cargo.toml b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/foo/Cargo.toml new file mode 100644 index 00000000000..c0a92ea13b9 --- /dev/null +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/foo/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "foo" +version = "0.0.0" +edition = "2024" diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/foo/src/lib.rs b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/foo/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/stderr.term.svg b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/stderr.term.svg new file mode 100644 index 00000000000..0f967ee3fa5 --- /dev/null +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/stderr.term.svg @@ -0,0 +1,27 @@ + + + + + + + error: cannot override workspace dependency with `--default-features`, either change `workspace.dependencies.foo.default-features` or define the dependency exclusively in the package's manifest + + + + + + diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index bd9024466ac..1ae817db983 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -48,6 +48,7 @@ mod git_rev; mod git_tag; mod help; mod infer_prerelease; +mod inherit_no_default_features_2024_edition; mod invalid_arg; mod invalid_git_name; mod invalid_inherited_dependency; From d4b668c173c3df535df96f44b01c92299e5d7d23 Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Mon, 22 Jun 2026 00:02:01 +0200 Subject: [PATCH 3/7] feat(add): override inherited default-features in 2024 `cargo add --no-default-features` (or `--default-features`) against a dependency inherited from `[workspace.dependencies]` is now allowed when the package is on Edition 2024+, matching the manifest behavior. Earlier editions still error, pointing the user to the workspace manifest. Signed-off-by: 0xPoe --- src/ops/cargo_add/mod.rs | 13 ++++++++----- .../inherit_no_default_features_2024_edition/mod.rs | 2 +- .../out/bar/Cargo.toml | 1 + .../stderr.term.svg | 6 +++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/ops/cargo_add/mod.rs b/src/ops/cargo_add/mod.rs index b200c7bfa1b..fbfe1e54ba2 100644 --- a/src/ops/cargo_add/mod.rs +++ b/src/ops/cargo_add/mod.rs @@ -28,6 +28,7 @@ use crate::util::OptVersionReq; use crate::util::cache_lock::CacheLockMode; use crate::util::edit_distance; use crate::util::style; +use crate::workspace::Edition; use crate::workspace::Feature; use crate::workspace::FeatureValue; use crate::workspace::Features; @@ -503,7 +504,7 @@ fn resolve_dependency( } if let Some(Source::Workspace(_)) = dependency.source() { - check_invalid_ws_keys(dependency.toml_key(), arg)?; + check_invalid_ws_keys(dependency.toml_key(), arg, spec.manifest().edition())?; } let version_required = dependency.source().and_then(|s| s.as_registry()).is_some(); @@ -703,14 +704,16 @@ fn fuzzy_lookup( /// When { workspace = true } you cannot define other keys that configure /// the source of the dependency such as `version`, `registry`, `registry-index`, -/// `path`, `git`, `branch`, `tag`, `rev`, or `package`. You can also not define -/// `default-features`. +/// `path`, `git`, `branch`, `tag`, `rev`, or `package`. +/// Prior to Edition 2024 (RFC 3945) `default-features` was also forbidden; +/// from Edition 2024 onwards a package-level `default-features` overrides the +/// workspace value, so it is allowed. /// /// Only `default-features`, `registry` and `rename` need to be checked /// for currently. This is because `git` and its associated keys, `path`, and /// `version` should all bee checked before this is called. `rename` is checked /// for as it turns into `package` -fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp) -> CargoResult<()> { +fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp, edition: Edition) -> CargoResult<()> { fn err_msg(toml_key: &str, flag: &str, field: &str) -> String { format!( "cannot override workspace dependency with `{flag}`, \ @@ -719,7 +722,7 @@ fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp) -> CargoResult<()> { ) } - if arg.default_features.is_some() { + if arg.default_features.is_some() && edition < Edition::Edition2024 { anyhow::bail!( "{}", err_msg(toml_key, "--default-features", "default-features") diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/mod.rs b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/mod.rs index 7c2925dd4ec..976fe41d8f5 100644 --- a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/mod.rs +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/mod.rs @@ -17,7 +17,7 @@ fn case() { .args(["foo", "--no-default-features", "-p", "bar"]) .current_dir(cwd) .assert() - .failure() + .success() .stdout_eq(str![""]) .stderr_eq(file!["stderr.term.svg"]); diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/Cargo.toml b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/Cargo.toml index 3bd66f7b4a1..04c5dac7ef9 100644 --- a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/Cargo.toml +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/out/bar/Cargo.toml @@ -4,3 +4,4 @@ version = "0.0.0" edition = "2024" [dependencies] +foo = { workspace = true, default-features = false } diff --git a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/stderr.term.svg b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/stderr.term.svg index 0f967ee3fa5..6f612421cab 100644 --- a/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/stderr.term.svg +++ b/tests/testsuite/cargo_add/inherit_no_default_features_2024_edition/stderr.term.svg @@ -1,8 +1,8 @@ - +