diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6d9a3b8dc..1e82c3521b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Changed +- The unstable `required_version` configuration option now defaults to a caret (`^`) semver requirement instead of an exact match when no comparator is given, matching how Cargo treats dependency version requirements. Use the explicit `=` operator (e.g. `required_version = "=1.2.3"`) to opt back into exact matching [#6729](https://github.com/rust-lang/rustfmt/issues/6729) + ## [1.10.0] 2026-07-21 diff --git a/Configurations.md b/Configurations.md index 189389fe143..f0773dd1500 100644 --- a/Configurations.md +++ b/Configurations.md @@ -2465,14 +2465,28 @@ specific version of rustfmt is used in your CI, use this option. - **Default value**: `CARGO_PKG_VERSION` - **Possible values**: `semver` compliant values, such as defined on [semver.org](https://semver.org/). + A value with no comparator, such as `"1.0.0"`, defaults to a caret (`^`) + requirement, matching how Cargo treats dependency version requirements. - **Stable**: No (tracking issue: [#3386](https://github.com/rust-lang/rustfmt/issues/3386)) -#### Match on exact version: +#### New minor or patch versions (default behavior): + +```toml +required_version="^1.0.0" +``` + +This is the default behavior, so that is equivalent to: ```toml required_version="1.0.0" ``` +#### Match on exact version: + +```toml +required_version="=1.0.0" +``` + #### Higher or equal to: ```toml diff --git a/src/config/mod.rs b/src/config/mod.rs index a3f9842cd4f..96aac3c7549 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -242,7 +242,10 @@ impl PartialConfig { } fn check_semver_version(range_requirement: &str, actual: &str) -> bool { - let mut version_req = match semver::VersionReq::parse(range_requirement) { + // A bare version (no comparator prefix) is left as `semver`'s own default, + // which is a caret requirement, matching how Cargo treats dependency version + // requirements. + let version_req = match semver::VersionReq::parse(range_requirement) { Ok(r) => r, Err(e) => { eprintln!("Error: failed to parse required version {range_requirement:?}: {e}"); @@ -257,24 +260,6 @@ fn check_semver_version(range_requirement: &str, actual: &str) -> bool { } }; - range_requirement - .split(',') - .enumerate() - .for_each(|(i, label)| { - // the label refers to the current comparator - let Some(comparator) = version_req.comparators.get_mut(i) else { - return; - }; - - // semver crate handles "1.0.0" as "^1.0.0", and we want to treat it as "=1.0.0" - // because of this, we need to iterate over the comparators, and change each one - // that has "default caret operator" to an exact operator - // this condition overrides the "default caret operator" of semver create. - if !label.starts_with('^') && comparator.op == semver::Op::Caret { - comparator.op = semver::Op::Exact; - } - }); - version_req.matches(&actual_version) } @@ -1552,15 +1537,27 @@ make_backup = false use super::*; #[test] - fn test_exact_version_match() { + fn test_default_caret_match() { + // A bare version (no operator) defaults to a caret requirement, + // matching how Cargo treats dependency version requirements. assert!(check_semver_version("1.0.0", "1.0.0")); - assert!(!check_semver_version("1.0.0", "1.1.0")); - assert!(!check_semver_version("1.0.0", "1.0.1")); + assert!(check_semver_version("1.0.0", "1.1.0")); + assert!(check_semver_version("1.0.0", "1.0.1")); assert!(!check_semver_version("1.0.0", "2.1.0")); assert!(!check_semver_version("1.0.0", "0.1.0")); assert!(!check_semver_version("1.0.0", "0.0.1")); } + #[test] + fn test_explicit_exact_match() { + assert!(check_semver_version("=1.0.0", "1.0.0")); + assert!(!check_semver_version("=1.0.0", "1.1.0")); + assert!(!check_semver_version("=1.0.0", "1.0.1")); + assert!(!check_semver_version("=1.0.0", "2.1.0")); + assert!(!check_semver_version("=1.0.0", "0.1.0")); + assert!(!check_semver_version("=1.0.0", "0.0.1")); + } + #[test] fn test_version_mismatch() { assert!(!check_semver_version("2.0.0", "1.0.0"));