From 4dbd2d2eaa7dc17debf25f53e057cf6c075cccde Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 13 Sep 2025 12:28:22 -0500 Subject: [PATCH 001/109] Fork cfg_version v2 skeleton --- text/0000-cfg-version.md | 103 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 text/0000-cfg-version.md diff --git a/text/0000-cfg-version.md b/text/0000-cfg-version.md new file mode 100644 index 00000000000..80ead4e074b --- /dev/null +++ b/text/0000-cfg-version.md @@ -0,0 +1,103 @@ +- Feature Name: (fill me in with a unique ident, `my_awesome_feature`) +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +One paragraph explanation of the feature. + +# Motivation +[motivation]: #motivation + +Any changes to Rust should focus on solving a problem that users of Rust are having. +This section should explain this problem in detail, including necessary background. + +It should also contain several specific use cases where this feature can help a user, and explain how it helps. +This can then be used to guide the design of the feature. + +This section is one of the most important sections of any RFC, and can be lengthy. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: + +- Introducing new named concepts. +- Explaining the feature largely in terms of examples. +- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or migration guidance. +- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. +- Discuss how this impacts the ability to read, understand, and maintain Rust code. Code is read and modified far more often than written; will the proposed feature make code easier to maintain? + +For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This is the technical portion of the RFC. Explain the design in sufficient detail that: + +- Its interaction with other features is clear. +- It is reasonably clear how the feature would be implemented. +- Corner cases are dissected by example. + +The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. + +# Drawbacks +[drawbacks]: #drawbacks + +Why should we *not* do this? + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- Why is this design the best in the space of possible designs? +- What other designs have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? +- If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain? + +# Prior art +[prior-art]: #prior-art + +Discuss prior art, both the good and the bad, in relation to this proposal. +A few examples of what this can include are: + +- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? +- For community proposals: Is this done by some other community and what were their experiences with it? +- For other teams: What lessons can we learn from what other communities have done here? +- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. + +This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. +If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. + +Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. +Please also take into consideration that rust sometimes intentionally diverges from common language features. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +# Future possibilities +[future-possibilities]: #future-possibilities + +Think about what the natural extension and evolution of your proposal would +be and how it would affect the language and project as a whole in a holistic +way. Try to use this section as a tool to more fully consider all possible +interactions with the project and language in your proposal. +Also consider how this all fits into the roadmap for the project +and of the relevant sub-team. + +This is also a good place to "dump ideas", if they are out of scope for the +RFC you are writing but otherwise related. + +If you have tried and cannot think of any future possibilities, +you may simply state that you cannot think of anything. + +Note that having something written down in the future-possibilities section +is not a reason to accept the current or a future RFC; such notes should be +in the section on motivation or rationale in this or subsequent RFCs. +The section merely provides additional information. From ecb34593e56c7b617c61effc9c0a1746185f57ed Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 14:14:42 -0500 Subject: [PATCH 002/109] Initial draft --- text/0000-cfg-version.md | 611 +++++++++++++++++++++++++++++++++++---- 1 file changed, 556 insertions(+), 55 deletions(-) diff --git a/text/0000-cfg-version.md b/text/0000-cfg-version.md index 80ead4e074b..6a1881fe0e6 100644 --- a/text/0000-cfg-version.md +++ b/text/0000-cfg-version.md @@ -1,103 +1,604 @@ -- Feature Name: (fill me in with a unique ident, `my_awesome_feature`) -- Start Date: (fill me in with today's date, YYYY-MM-DD) +- Feature Name: `cfg_version` +- Start Date: 2025-09-13 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +- Rust Issue: [rust-lang/rust#64796](https://github.com/rust-lang/rust/issues/64796) # Summary [summary]: #summary -One paragraph explanation of the feature. +Allow Rust-version conditional compilation by adding a built-in `--cfg rust=` and a minimum-version `#[cfg]` predicate. + +Say this was added before 1.70, you could do: +```toml +[target.'cfg(not(version(rust, "1.70")))'.dependencies"] +is-terminal = "0.4.16" +``` + +```rust +fn is_stderr_terminal() -> bool { + #[cfg(version(rust, "1.70"))] + use std::io::IsTerminal as _; + #[cfg(not(version(rust, "1.70")))] + use is_terminal::IsTerminal as _; + + std::io::stderr().is_terminal() +} +``` + +This supersedes the `cfg_version` subset of [RFC 2523](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html). # Motivation [motivation]: #motivation -Any changes to Rust should focus on solving a problem that users of Rust are having. -This section should explain this problem in detail, including necessary background. +These problems mostly have solutions today through third-party crates or other patterns but +- The workarounds for Rust-version-specific dependencies are less straightforward and difficult to get right. +- That requires knowledge of them when users expect this to "just work" like in other ecosystems +- They slow down build times, requiring at least one build script to be fully built (even in `cargo check`) and then executed. In one sample "simple webserver", in the dependency tree there were 10 build scripts and 2 proc-macros built for the sake of version detection. + +## Use cases + +In considering use cases, there can be different needs. + +Specificity: +- Display version: the format is opaque and only intended for showing to users +- Programmatic version: the format is specified and relied on for comparing values + +Semantics: +- Language version: versioning of expected / defined behavior, based on the canonical compiler +- Vendor name/version: identifying the specific compiler being used + +We are scoping this RFC down to "language version" but considering "vendor version" +as it can be approximated by the "language version" and in case it breaks any ties in decisions. + +### Supporting an MSRV Policy + +Requires: programmatic, language version + +When maintaining an [MSRV policy](https://doc.rust-lang.org/cargo/reference/rust-version.html#setting-and-updating-rust-version), +maintainers can be caught between: +- The needs of users on older toolchains, regardless of the reason +- The needs of users on the latest toolchain that expect integration with new features (e.g. `Error` in `core`) or faster compile times (dropping `is-terminal` dep in favor of `std::io::IsTerminal`) +- The expectations they have set with their policy + +For a simple case, like `Error` in `core`, +maintainers want to conditionally add the `impl core::error::Error` if its supported. + +In cases like `std::io::IsTerminal`, +maintainers need to trim dependencies in Cargo for newer Rust versions to maintain reasonable build times for users on newer toolchains. + +A challenge with this is that in order to solve this, +we need to add a new feature that requires waiting for an MSRV bump before it can be used. +Being able to check for the presence of this feature would allow immediate adoption. + +### Testing proc-macros -It should also contain several specific use cases where this feature can help a user, and explain how it helps. -This can then be used to guide the design of the feature. +*(non-motivating)* -This section is one of the most important sections of any RFC, and can be lengthy. +Requires: programmatic, vendor version +- Can be approximated by using the language version + +Error reporting can be a major usability issue for proc-macros. +Packages like [`trybuild`](https://crates.io/crates/trybuild) exist to demonstrate and track +the quality of errors reported by proc-macros by compiling sample code and snapshotting the compiler output. +However, compiler output is dependent on the vendor and changes from release to release, so maintainers need to restrict the tests to specific Rust versions. + +For example, in `clap`'s [`derive_ui`](https://github.com/clap-rs/clap/blob/master/tests/derive_ui.rs) test: +```rust +#[cfg(feature = "derive")] +#[rustversion::attr(not(stable(1.89)), ignore)] // STABLE +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/derive_ui/*.rs"); +} +``` + +### Working around compiler bugs + +*(non-motivating)* + +Requires: programmatic, vendor version +- Can be approximated by using the language version + +At times, a vendor's compiler has bugs that need to be worked around, +e.g. see [error-chain#101](https://github.com/rust-lang-deprecated/error-chain/issues/101). + +### Build information + +*(non-motivating)* + +Requires: display, vendor version +- Can be approximated by using the language version + +Some applications choose to include build information in their verbose-version or `--bugreport`. +This can include the compiler vendor and version used to build the application. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: +When using a new language or standard library feature, +circumstances may warrant doing so while maintaining an existing [MSRV](https://doc.rust-lang.org/cargo/reference/rust-version.html), +rather than raising to what the language or standard library feature needs. +This can be accomplished by conditionally compiling the code for that feature. + +For instance, say you have an MSRV of 1.10 and `#[cfg(version)]` feature was available in 1.20, you would have been able to do: +```rust +#[cfg_attr(version(rust, "1.27"), must_use)] +fn double(x: i32) -> i32 { + 2 * x +} + +fn main() { + double(4); + // warning: unused return value of `double` which must be used + // ^--- This warning only happens if we are on Rust >= 1.27. +} +``` + +> Side note: if we also had [RFC 3804](https://github.com/rust-lang/rfcs/pull/3804), +> we can give this condition a semantic name and avoid duplicating it, reducing the chance of bugs: +> ```rust +> #[cfg_alias(must_use_exists, version(rust, "1.27"))] +> +> #[cfg_attr(must_use_exists, must_use)] +> fn double(x: i32) -> i32 { +> 2 * x +> } +> +> fn main() { +> double(4); +> // warning: unused return value of `double` which must be used +> // ^--- This warning only happens if we are on Rust >= 1.27. +> } +> ``` + +Now, let's say `#[cfg(version)]` was stabilized in Rust 1.27 or later, you can check for support for it with: +```rust +#[cfg_attr(rust, cfg_attr(version(rust, "1.27"), must_use))] +fn double(x: i32) -> i32 { + 2 * x +} + +fn main() { + double(4); + // warning: unused return value of `double` which must be used + // ^--- This warning only happens if we are on Rust >= 1.27. +} +``` +However, this would produce a `unexpected_cfgs` lint and you would need to add the following to `Cargo.toml`: +```toml +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } +``` -- Introducing new named concepts. -- Explaining the feature largely in terms of examples. -- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. -- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. -- Discuss how this impacts the ability to read, understand, and maintain Rust code. Code is read and modified far more often than written; will the proposed feature make code easier to maintain? +Say you were wanting to test out `#[must_use]` after it got stabilized on nightly to provide feedback and to be ready for when it hits stable, there would be no change to the examples above. +Nightly compiler releases report themselves as the version they will be on hitting the stable channel. -For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. +On the flip side, you might be bisecting nightlies or pinning to a 1.27 nightly before `#[must_use]` was stabilized. +You would have the sad predicament of not being able to use `double` because you would get a feature-gate error. +In that case, you can pass `-Z assume-incomplete-release` to the compiler so that 1.27 nightly reports itself as being 1.26 and `#[must_use]` won't be applied within `double`. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This is the technical portion of the RFC. Explain the design in sufficient detail that: +## `version` cfg predicate -- Its interaction with other features is clear. -- It is reasonably clear how the feature would be implemented. -- Corner cases are dissected by example. +A `version` cfg predicate will be added to Rust. +As Cargo mirrors Rust's `#[cfg]` syntax, it too will gain this predicate. -The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. +The [syntax](https://doc.rust-lang.org/reference/conditional-compilation.html#grammar-ConfigurationPredicate) is: +``` +ConfigurationVersion -> `version` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` +``` + +If `IDENTIFIER` has no value or is undefined, this will evaluate to `false`. + +If `IDENTIFIER` is not a valid [SemVer](https://semver.org/) value, this will evaluate to `false`. + +If the string literal is does not conform to the syntax from `` to `..-` where the first three fields must be integers, this will evaluate to `false`. +Note that this excludes support for the `+build` field. + +Otherwise, the `IDENTIFIER` will be compared to the string literal according to +[Cargo's `>=` version requirements](https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#comparison-requirements). +For example, `#[cfg(version(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. + +See also `--check-cfg`. + +## `--check-cfg` + +A new predicate will be added of the form: +``` +ConfigurationVersion -> `version` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` +``` + +The syntax for the contents of the string literal is a SemVer value without the build field. + +This will specify that the given `--cfg` is valid for all values that match: +- SemVer syntax +- from the specified version and up + +When the given `--cfg` is used with the `version` predicate: +- the string literal should not be of a syntax that evaluates to `false` +- the minimum version requirement must specify a subset of what the `--check-cfg` specifies + +So given `--check-cfg 'cfg(foo, values(version("1.95.0")))'`, +- ✅ `#[cfg(foo = "1.100.0")]` +- ⚠️ `#[cfg(foo = "1.0")]`: not SemVer syntax +- ✅ `#[cfg(version(foo, "1.95.0"))]` +- ✅ `#[cfg(version(foo, "1.100.0"))]` +- ✅ `#[cfg(version(foo, "3.0.0"))]` +- ✅ `#[cfg(version(foo, "1.95"))]` +- ⚠️ `#[cfg(version(foo, "1.90.0"))]`: matches a superset of `--check-cfg` +- ⚠️ `#[cfg(version(foo, "1.95.0-0"))]`: matches a superset of `--check-cfg` +- ⚠️ `#[cfg(version(foo, "1"))]`: matches a superset of `--check-cfg` +- ⚠️ `#[cfg(version(foo, "bar"))]`: invalid string literal syntax + +## `rust` cfg + +A new built-in cfg `--cfg rust=` will be added by the compiler +that specifies the language version. +This will be the version of `rustc` with how the pre-release version is handled being unspecified. +We expect rustc to: +- Translate the `-nightly` pre-release to `-incomplete +- Strip the `-beta.5` pre-release + +`rust` will be specified as `--check-cfg 'cfg(rust, values(version("1.95.0")))'` +(or whatever version this gets stabilized in). + +This will be reported back through `--print-cfg`. + +Because this gets reported back in `--print-cfg`, +Cargo will expose `rust` in: +- build scripts as `CARGO_CFG_RUST` +- `target."cfg()".dependencies` + +## clippy + +Clippy has a [`clippy::incompatible_msrv`](https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv) lint +which will fire whenever a standard library item is used with a `#[stable(since)]` newer than `package.rust-version`. +However, it will be perfectly reasonable to use those items when guarded by a `#[cfg(version)]`. + +Clippy may wish to: +- Find a way to reduce false positives, e.g. evaluating the `cfg(version)`s that led to the item's usage or disabling the lint within `#[cfg(version)]` +- Suggest `#[cfg(version)]` in the `clippy::incompatible_msrv` diagnostic report (maybe along with offering to bump MSRV) # Drawbacks [drawbacks]: #drawbacks -Why should we *not* do this? +People may be using `--cfg rust` already and would be broken by this change. + +This does not include a solution for adopting this within `Cargo.toml` without waiting for an MSRV bump. + +Traditionally, maintainers only test their MSRV and latest, assuming those will catch every issue. +While that isn't always true today (e.g. some Cargo features go from "unknown" warning to "unstable" error to supported and MSRV might be in the warning phase), +having distinct implementations for different Rust versions can make the testing matrix more complex. +Tools like [`cargo hack`](https://crates.io/crates/cargo-hack) can help which can run commands on not just one toolchain version but also the every version starting with the MSRV with a command like `cargo hack --rust-version --version-step 1 check`. + +This does not help with probes for specific implementations of nightly features which still requires having a `build.rs`. + +Libraries could having ticking time bombs that accidentally break or have undesired behavior for some future Rust version that can't be found until we hit that version. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -- Why is this design the best in the space of possible designs? -- What other designs have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? -- If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain? +## `version` cfg predicate + +We could offer a `before` operator but that is already covered by `not(version)`. + +The `version` predicate's name doesn't convey what operation is happening and doesn't leave room for other version operators (e.g. a `before` to go with `since`). +We could call this `since`, `minimum`, or support comparison operators in the spirit of [RFC 3796](https://github.com/rust-lang/rfcs/pull/3796). +The challenge is dealing with clarity around the meaning, especially if we gain support for other data types in `cfg`, like integers, which is of interest for embedded development. +`since` would fit in with `deprecated`. + +We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(version("1.95"))]` as a shorthand. +However, this would look confusing in Cargo and doesn't seem like its offering enough of a benefit to be worth the costs. + +The `ConfigurationVersion` is sloppy with the string literal's syntax (relying on `--check-cfg`) so that +- Allows evolution without requiring an MSRV bump +- Its consistent with other predicates, e.g. `#[cfg(foo = "1.0")]` + +If we were stricter on the syntax, +we could allow for version numbers to be directly accepted, without quotes +(e.g. `#[cfg(version(rust, 1.95.0))]`). +If we ever decided to support operators (e.g.`#[cfg(version(rust, "=1.95.0"))]`), then we'd need to decide if those also go outside the string or then require a string, being inconsistent. +This would also be inconsistent with other uses of `cfg`s +*but* maybe that would just be the start to natively supporting more types in `cfg`, +like integers which are of interest to embedded folks. + +## `--check-cfg` + +The `--check-cfg` predicate and the value for `rust` ensures users get warnings about +- Invalid syntax +- Using this with versions from before its supported, e.g. `#[cfg(version(rust, "1.0.0")]` + +`--check-cfg` requires a SemVer version, rather than a version requirement, +in case we want the future possibility of relaxing SemVer versions +*and* we want to infer from the fields used in `--check-cfg` to specify the maximum number of fields accepted in comparisons. + +We could have the `check-cfg` `version` predicate only apply to the `cfg` `version` predicate, +causing `#[cfg(rust = "1.100.0")]` to warn. +However, +- the `version` predicates are a general feature intended to be used with other version numbers where exact matches may be appropriate. +- this would get in the way of approximating the vendor version by the language version for working around compiler bugs and snapshotting of compiler output. + +Possibly there could be a clippy lint specifically about `rust = ""`. +Alternatively, we could try to find a way to structure `--check-cfg` to allow the person defining the `cfg` to decide whether it can be use with `=` or not. +One way of doing this is by allowing the `check-cfg` `version` predicate outside of the `values` predicate, +meaning it works with the `cfg` `version` predicate and not the `=` operator. +Another way would be for the `check-cfg` version predicate to never work with `=` but to instead +allow operators inside of the `cfg` `version` predicate, e.g. `#[cfg(version(rust, "=1.95.0"))]`. + +`--check-cfg` will cause the following to warn: +```rust +fn is_stderr_terminal() -> bool { + #[cfg(rust)] + #[cfg(version(rust, "1.70"))] + use std::io::IsTerminal as _; + #[cfg(rust)] + #[cfg(not(version(rust, "1.70")))] + use is_terminal::IsTerminal as _; + #[cfg(not(rust))] + use is_terminal::IsTerminal as _; + + std::io::stderr().is_terminal() +} +``` + +To allow checking for the presence of `rust`, add the following to your `Cargo.toml`: +```toml +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } +``` +Alternatively, we could have the built-in `--check-cfg` include `values(none())` but: +- When building on an old version, users will see the warning and will likely want to add it anyways. +- We lose out on `--check-cfg` identifying misused. + Instead, we may wish to add a dedicated predicate intended for "is set". + +## `rust` cfg + +While there was concern over `rust` appearing in the name of `cfg(rust_version("1.95"))`, +I feel that `rust` as its own entity makes sense and avoids that problem. + +### Pre-release + +When translating `rustc --version` to a Language version, we have several choices when it comes to pre-releases, including: +- Treat the nightly as fully implementing that Language version +- Treat the nightly as not implementing that Language version at all, only the previous +- Leave a marker that that Language version is incomplete, while the previous Language version is complete + +In RFC 2523, this was left as an +[unresolved question](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html#unresolved-questions). + +The initial implementation treated nightlies as complete. +This was [changed to incomplete](https://github.com/rust-lang/rust/pull/72001) after +[some discussion](https://github.com/rust-lang/rust/issues/64796#issuecomment-624673206). +In particular, this is important for +- the case of package `bleeding-edge` starting to use a new feature behind `#[cfg(version)]` and package `nightly-only` has their toolchain pinned to a nightly before the feature was stabilized (to ensure consistent behavior of unstable features), package `nightly-only` cannot add or update their dependency on `bleeding-edge` without getting a "feature gate needed" error. +- bisecting nightlies. + +This was [changed back to complete](https://github.com/rust-lang/rust/pull/81468) after +[some more discussion](https://github.com/rust-lang/rust/issues/64796#issuecomment-634546711). +In particular, this is important for +- keeping friction down for packages preparing for stabilized-on-nightly features as their `#[cfg(version)]`s can be inserted and "just work" which can be important for getting feedback quickly while the feature is easier to adapt to feedback that can be gained from these users + - releasing the package while its in this state puts it at risk to be broken if the feature is changed after stabilization + +For RFC 2523, they settled on pre-releases being incomplete, +favoring maintainers to adopt stabilized-on-nightly features immediately +while letting people on pinned nightlies or bisecting nightlies to set a `-Z` to mark the version as incomplete. + +In this RFC, we settled translating `-nightly` to `-incomplete` because: +- Maintainers can adopt stabilized-on-nightly features with `#[cfg(version(rust, "1.100.0-0"))]` (the lowest pre-release for `1.100.0`), keeping friction low while explicitly acknowledging that the unstable feature may change +- Allows build scripts to experiment with other logic without less chance of needing to invoke `rustc` (e.g. detecting nightly) +- It provides extra context when approximating the vendor version from the language version when populating build information + +We called the pre-release `-incomplete` to speak to the relationship to the language version. +Other terms like `partial` could as easily apply. +The term `-nightly` would be more discoverable but would convey more of a relationship to the vendor than the language. + +As for differentiating between nightlies, +that corresponds more to the vendor version than the language version, +so we do not include that information. + +## Alternative designs + +### `cfg(rust_version(1.95))` + +*(this is [RFC 2523](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html))* + +Add a new predicate that knows the current Rust version and can compare a value against it. +Cargo would need to duplicate this lint. + +A lint would be needed to ensure the version is newer than when the predicate was added. + +To support Rust versions from before this predicate was added, +we could add `--cfg has_rust_version`. + +On the [stabilization issue](https://github.com/rust-lang/rust/pull/141766), +there was concern about the name "rust" in this predicate not fitting in with the rest of the language. +However, dropping it to `version` would make things awkward in Cargo where there wouldn't be enough context for which item's `version` is being referred to. +There is also a future possibility of better integrating dependency versions into the language. +If done, then `version` may be come more ambiguous even in Rust. +For example, if Cargo told rustc the minimum compatible version for a dependency, `#[deprecated(since)]`` warnings could not emit if the minimum version bound is lower than `since`. +Similarly, if we stabilized `#[stable(since)]`, a linter could report when a version requirement is too low. + +We could rename this to `version` and stabilize it as-is, +with this RFC being a future possibility that adds an optional second parameter for specifying which version is being referred to. + +### `cfg(rust = "1.95")` + +*(this [idea](https://github.com/rust-lang/rust/pull/141766#issuecomment-2940720778) came up on the stabilization PR for RFC 2523)* + +`rust` could represent the "versions supported" and would be a set of all versions, `.` and `..`, +between the version it was first supported up to the current version, +making the `=` operate as a "contains" operator, +rather than an equality operator, +like with `#[cfg(feature = "std")]`. +This was proposed to allow `#[cfg_attr(rust, cfg(rust = "1.95"))]` to more naturally allow adoption before the feature is stabilized. + +This could be implemented statically, by hard coding the versions. +This would work with `--print-cfg` and so would automatically work with Cargo. +However, there would `unexpected_cfgs` warnings if someone specified a point release unknown to the current toolchain. +As for `--check-cfg`, it would either hard-code the list of potential future version up to a certain limit, have a new predicate, or be handled through a different lint mechanism. +The list of `--print-check-cfg` items would be large and the list of `--print-cfg` items would only grow. +We could drop support for patch releases but then maintainers couldn't approximate the vendor version to work around bugs or to report build information. + +Alternatively, whether a value is contained in `rust` could be determined dynamically. +`rust` would not show up in `--print-cfg`. +As for `--check-cfg`, it would either need to also be dynamic (and not printed by `--print-check-cfg`), a new predicate, or handled through a different lint mechanism. +Cargo would need to duplicate this dynamic value. +**Note that this in was [rejected in RFC 2523](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html#the-bikeshed---argument-syntax) due to this dynamic nature.** + +The "contains" behavior of `=` is not too obvious. +For the `feature` set, +I presume it was named in the singular +(as opposed to being consistent with the `[features]` table or plural to convey it is a set) +to fit in with looking like an equality operation (`#[cfg(feature = "foo")]`). +We could add a new predicate to convey set containment. # Prior art [prior-art]: #prior-art -Discuss prior art, both the good and the bad, in relation to this proposal. -A few examples of what this can include are: +## Rust + +### `rustversion` -- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? -- For community proposals: Is this done by some other community and what were their experiences with it? -- For other teams: What lessons can we learn from what other communities have done here? -- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. +[crates.io](https://crates.io/crates/rustversion) +- MSRV of 1.31 +- proc-macro that queries rustc through a build script +- 531 reverse dependencies with ~260 million downloads -This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. -If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. +Provides +- channel checks: `#[rustversion::stable]`, `#[rustversion::beta]`, `#[rustversion::nightly]` +- equality checks: `#[rustversion::stable(1.34)]`, `#[rustversion::nightly(2025-01-01)]` +- `>=` version: `#[rustversion::since(1.34)]` +- `>=` nightly: `#[rustversion::since(2025-01-01)]` +- `<` version: `#[rustversion::before(1.34)]` +- `<` nightly: `#[rustversion::before(2025-01-01)]` -Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. -Please also take into consideration that rust sometimes intentionally diverges from common language features. +### `rustc_version` + +[crates.io](https://crates.io/crates/rustc_version) +- MSRV of 1.32 +- library for use in build scripts for conditional compilation +- 680 reverse dependencies with ~330 million downloads + +Accessible +- Channel +- Version +- Release metadata (e.g. commit hash) + +### `version_check` + +[crates.io](https://crates.io/crates/version_check) +- library for use in build scripts for conditional compilation +- 152 reverse dependencies with ~450 million downloads + +Accessible +- Query channel, version, and date +- Min, max, and equality operators for the above + +### Polyfills + +The `is_terminal_polyfill` maintains +[versions](https://crates.io/crates/is_terminal_polyfill/versions) +for each MSRV with distinct implementations, +relying on the [MSRV-aware resolver](https://rust-lang.github.io/rfcs/3537-msrv-resolver.html) +to pick the appropriate version. + +### `shadow-rs` + +[crates.io](https://crates.io/crates/shadow-rs) +- library for use in build scripts for release information +- 81 reverse dependencies with ~5 million downloads + +Accessible +- Release information +- Channel +- Cargo version + +### `vergen` + +[crates.io](https://crates.io/crates/vergen) +- library for use in build scripts for release information +- 182 reverse dependencies with ~26 million downloads + +Accessible +- Channel +- Commit date +- Commit hash +- LLVM version +- Version + +## Other + +Python +- Programmatic version: [`sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info) +- Vendor display version: [`sys.version`](https://docs.python.org/3/library/sys.html#sys.version) +- [Dependency specifiers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/) + - e.g. `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version < "2.7"` + +C++ +- Numeric value representing the version of the C++ standard: [`__cplusplus`](https://en.cppreference.com/w/cpp/preprocessor/replace): + +C: +- Implementation-defined value of the C++ standard: [__STDC_VERSION__](https://en.cppreference.com/w/cpp/preprocessor/replace) + +Haskell: +- Numeric value representing the vendor's version, e.g. `#if __GLASGOW_HASKELL__ >= 706` # Unresolved questions [unresolved-questions]: #unresolved-questions -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? - # Future possibilities [future-possibilities]: #future-possibilities -Think about what the natural extension and evolution of your proposal would -be and how it would affect the language and project as a whole in a holistic -way. Try to use this section as a tool to more fully consider all possible -interactions with the project and language in your proposal. -Also consider how this all fits into the roadmap for the project -and of the relevant sub-team. +- In the future the `--check-cfg` `version()` predicate could make the minimum-version field optional, + matching all version numbers. + +## Relaxing SemVer + +Instead of requiring the `IDENTIFIER` in the `version` predicate to be strictly SemVer `major.minor.patch`, +we could allow abbreviated forms like `major.minor` or even `major`. +This would make the predicate more inclusive for other cases, like `edition`. + +## Vendor name and version + +We could add `--cfg`s for the compiler vendor name and version. +In addition to the use cases given in the Motivation section, +this will allow users to check for specific nightly versions. + +Some challenges for this with `rustc --version`: +- Nightly versions for a given release are mutable, + all mapping to the `-nightly` pre-release version rather than including the date within the pre-release +- This does not conform to SemVer's precedence rules, + as `-nightly` is an older version than `-beta.4` while [SemVer's precedence rules](https://semver.org/#spec-item-11) say the opposite + +## `#[cfg(nightly)]` + +Depending on what is meant by this, +we either need the language version or the vendor name and version as well as a way to check for the presence of `pre-release`. + +See also [`#[cfg(nightly)]`](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html#cfgnightly) in the previous RFC. + +## `cfg_target_version` + +Instead of defining a new `#[cfg]` predicate, [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750) +could reuse the `#[cfg(version)]` predicate. + +As not all systems use SemVer, we can either +- Contort the version into SemVer + - This can run into problems either with having more precision (e.g. `120.0.1.10` while SemVer only allows `X.Y.Z`) or post-release versions (e.g. `1.2.0.post1` which a SemVer predicate would treat as a pre-release). +- Add an optional third field for specifying the version format (e.g. `#[cfg(version(windows, "10.0.10240", )]`) +- Make `--check-cfg` load-bearing by having the version policy name be specified in the `--check-cfg` predicate + +## Provide a way to get a `--cfg`s value -This is also a good place to "dump ideas", if they are out of scope for the -RFC you are writing but otherwise related. +Similar to how `cfg!` allows doing conditionals in Rust code, provide a "`cfg_value!`" for reading the value. +On top of [other use cases](https://internals.rust-lang.org/t/pre-rfc-mutually-excusive-global-features/19618) for `cfg_value!`, +this would allow an application to approximate the vendor version `--bugreport` / `-v --version` without a build script. -If you have tried and cannot think of any future possibilities, -you may simply state that you cannot think of anything. +## Conditional compilation for dependency versions -Note that having something written down in the future-possibilities section -is not a reason to accept the current or a future RFC; such notes should be -in the section on motivation or rationale in this or subsequent RFCs. -The section merely provides additional information. +As the ecosystem grows and matures, +the Rust language and standard library may not be the only dependencies users wish to support multiple versions of. +We may want to allow `#(cfg(version(serde, "1.0.900")]`. From 236f2a589d4cfd2aac7363bb3bf4ad63f94b060e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 14:25:22 -0500 Subject: [PATCH 003/109] Rename the version predicate to since --- text/0000-cfg-version.md | 112 +++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/text/0000-cfg-version.md b/text/0000-cfg-version.md index 6a1881fe0e6..f7574f079cf 100644 --- a/text/0000-cfg-version.md +++ b/text/0000-cfg-version.md @@ -10,15 +10,15 @@ Allow Rust-version conditional compilation by adding a built-in `--cfg rust= bool { - #[cfg(version(rust, "1.70"))] + #[cfg(since(rust, "1.70"))] use std::io::IsTerminal as _; - #[cfg(not(version(rust, "1.70")))] + #[cfg(not(since(rust, "1.70")))] use is_terminal::IsTerminal as _; std::io::stderr().is_terminal() @@ -121,9 +121,9 @@ circumstances may warrant doing so while maintaining an existing [MSRV](https:// rather than raising to what the language or standard library feature needs. This can be accomplished by conditionally compiling the code for that feature. -For instance, say you have an MSRV of 1.10 and `#[cfg(version)]` feature was available in 1.20, you would have been able to do: +For instance, say you have an MSRV of 1.10 and `#[cfg(since)]` feature was available in 1.20, you would have been able to do: ```rust -#[cfg_attr(version(rust, "1.27"), must_use)] +#[cfg_attr(since(rust, "1.27"), must_use)] fn double(x: i32) -> i32 { 2 * x } @@ -138,7 +138,7 @@ fn main() { > Side note: if we also had [RFC 3804](https://github.com/rust-lang/rfcs/pull/3804), > we can give this condition a semantic name and avoid duplicating it, reducing the chance of bugs: > ```rust -> #[cfg_alias(must_use_exists, version(rust, "1.27"))] +> #[cfg_alias(must_use_exists, since(rust, "1.27"))] > > #[cfg_attr(must_use_exists, must_use)] > fn double(x: i32) -> i32 { @@ -152,9 +152,9 @@ fn main() { > } > ``` -Now, let's say `#[cfg(version)]` was stabilized in Rust 1.27 or later, you can check for support for it with: +Now, let's say `#[cfg(since)]` was stabilized in Rust 1.27 or later, you can check for support for it with: ```rust -#[cfg_attr(rust, cfg_attr(version(rust, "1.27"), must_use))] +#[cfg_attr(rust, cfg_attr(since(rust, "1.27"), must_use))] fn double(x: i32) -> i32 { 2 * x } @@ -181,14 +181,14 @@ In that case, you can pass `-Z assume-incomplete-release` to the compiler so tha # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -## `version` cfg predicate +## `since` cfg predicate -A `version` cfg predicate will be added to Rust. +A `since` cfg predicate will be added to Rust. As Cargo mirrors Rust's `#[cfg]` syntax, it too will gain this predicate. The [syntax](https://doc.rust-lang.org/reference/conditional-compilation.html#grammar-ConfigurationPredicate) is: ``` -ConfigurationVersion -> `version` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` +ConfigurationVersion -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` ``` If `IDENTIFIER` has no value or is undefined, this will evaluate to `false`. @@ -200,7 +200,7 @@ Note that this excludes support for the `+build` field. Otherwise, the `IDENTIFIER` will be compared to the string literal according to [Cargo's `>=` version requirements](https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#comparison-requirements). -For example, `#[cfg(version(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. +For example, `#[cfg(since(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. See also `--check-cfg`. @@ -208,7 +208,7 @@ See also `--check-cfg`. A new predicate will be added of the form: ``` -ConfigurationVersion -> `version` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` +ConfigurationVersion -> `since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` ``` The syntax for the contents of the string literal is a SemVer value without the build field. @@ -217,21 +217,21 @@ This will specify that the given `--cfg` is valid for all values that match: - SemVer syntax - from the specified version and up -When the given `--cfg` is used with the `version` predicate: +When the given `--cfg` is used with the `since` predicate: - the string literal should not be of a syntax that evaluates to `false` - the minimum version requirement must specify a subset of what the `--check-cfg` specifies -So given `--check-cfg 'cfg(foo, values(version("1.95.0")))'`, +So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, - ✅ `#[cfg(foo = "1.100.0")]` - ⚠️ `#[cfg(foo = "1.0")]`: not SemVer syntax -- ✅ `#[cfg(version(foo, "1.95.0"))]` -- ✅ `#[cfg(version(foo, "1.100.0"))]` -- ✅ `#[cfg(version(foo, "3.0.0"))]` -- ✅ `#[cfg(version(foo, "1.95"))]` -- ⚠️ `#[cfg(version(foo, "1.90.0"))]`: matches a superset of `--check-cfg` -- ⚠️ `#[cfg(version(foo, "1.95.0-0"))]`: matches a superset of `--check-cfg` -- ⚠️ `#[cfg(version(foo, "1"))]`: matches a superset of `--check-cfg` -- ⚠️ `#[cfg(version(foo, "bar"))]`: invalid string literal syntax +- ✅ `#[cfg(since(foo, "1.95.0"))]` +- ✅ `#[cfg(since(foo, "1.100.0"))]` +- ✅ `#[cfg(since(foo, "3.0.0"))]` +- ✅ `#[cfg(since(foo, "1.95"))]` +- ⚠️ `#[cfg(since(foo, "1.90.0"))]`: matches a superset of `--check-cfg` +- ⚠️ `#[cfg(since(foo, "1.95.0-0"))]`: matches a superset of `--check-cfg` +- ⚠️ `#[cfg(since(foo, "1"))]`: matches a superset of `--check-cfg` +- ⚠️ `#[cfg(since(foo, "bar"))]`: invalid string literal syntax ## `rust` cfg @@ -242,7 +242,7 @@ We expect rustc to: - Translate the `-nightly` pre-release to `-incomplete - Strip the `-beta.5` pre-release -`rust` will be specified as `--check-cfg 'cfg(rust, values(version("1.95.0")))'` +`rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95.0")))'` (or whatever version this gets stabilized in). This will be reported back through `--print-cfg`. @@ -256,16 +256,17 @@ Cargo will expose `rust` in: Clippy has a [`clippy::incompatible_msrv`](https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv) lint which will fire whenever a standard library item is used with a `#[stable(since)]` newer than `package.rust-version`. -However, it will be perfectly reasonable to use those items when guarded by a `#[cfg(version)]`. +However, it will be perfectly reasonable to use those items when guarded by a `#[cfg(since)]`. Clippy may wish to: -- Find a way to reduce false positives, e.g. evaluating the `cfg(version)`s that led to the item's usage or disabling the lint within `#[cfg(version)]` -- Suggest `#[cfg(version)]` in the `clippy::incompatible_msrv` diagnostic report (maybe along with offering to bump MSRV) +- Find a way to reduce false positives, e.g. evaluating the `cfg(since)`s that led to the item's usage or disabling the lint within `#[cfg(since)]` +- Suggest `#[cfg(since)]` in the `clippy::incompatible_msrv` diagnostic report (maybe along with offering to bump MSRV) # Drawbacks [drawbacks]: #drawbacks People may be using `--cfg rust` already and would be broken by this change. +There are no compatibility concerns with predicate names. This does not include a solution for adopting this within `Cargo.toml` without waiting for an MSRV bump. @@ -281,16 +282,20 @@ Libraries could having ticking time bombs that accidentally break or have undesi # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -## `version` cfg predicate +## `since` cfg predicate -We could offer a `before` operator but that is already covered by `not(version)`. +We could offer a `before` operator but that is already covered by `not(since)`. -The `version` predicate's name doesn't convey what operation is happening and doesn't leave room for other version operators (e.g. a `before` to go with `since`). -We could call this `since`, `minimum`, or support comparison operators in the spirit of [RFC 3796](https://github.com/rust-lang/rfcs/pull/3796). -The challenge is dealing with clarity around the meaning, especially if we gain support for other data types in `cfg`, like integers, which is of interest for embedded development. -`since` would fit in with `deprecated`. +The `since` name was taken from +[rustversion](https://crates.io/crates/rustversion) and the `#[deprecated(since)]` / `#[stable(since)]` attributes. +This better conveys what operation is being performed than the original `version` name +and leaves room for related predicates like `before`. +We could also call this `minimum`, or support comparison operators in the spirit of [RFC 3796](https://github.com/rust-lang/rfcs/pull/3796). +The risk with a general word like `since` is if we gain support for other data types in cfgs, like integers for embedded development. +The name `since` might apply in some situations but not others and its unclear if we'd want to generalize it past versions. +While having a specific name avoids these concerns. -We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(version("1.95"))]` as a shorthand. +We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(since("1.95"))]` as a shorthand. However, this would look confusing in Cargo and doesn't seem like its offering enough of a benefit to be worth the costs. The `ConfigurationVersion` is sloppy with the string literal's syntax (relying on `--check-cfg`) so that @@ -299,8 +304,8 @@ The `ConfigurationVersion` is sloppy with the string literal's syntax (relying o If we were stricter on the syntax, we could allow for version numbers to be directly accepted, without quotes -(e.g. `#[cfg(version(rust, 1.95.0))]`). -If we ever decided to support operators (e.g.`#[cfg(version(rust, "=1.95.0"))]`), then we'd need to decide if those also go outside the string or then require a string, being inconsistent. +(e.g. `#[cfg(since(rust, 1.95.0))]`). +If we ever decided to support operators (e.g.`#[cfg(since(rust, "=1.95.0"))]`, see `--check-cfg`), then we'd need to decide if those also go outside the string or then require a string, being inconsistent. This would also be inconsistent with other uses of `cfg`s *but* maybe that would just be the start to natively supporting more types in `cfg`, like integers which are of interest to embedded folks. @@ -309,33 +314,34 @@ like integers which are of interest to embedded folks. The `--check-cfg` predicate and the value for `rust` ensures users get warnings about - Invalid syntax -- Using this with versions from before its supported, e.g. `#[cfg(version(rust, "1.0.0")]` +- Using this with versions from before its supported, e.g. `#[cfg(since(rust, "1.0.0")]` `--check-cfg` requires a SemVer version, rather than a version requirement, in case we want the future possibility of relaxing SemVer versions *and* we want to infer from the fields used in `--check-cfg` to specify the maximum number of fields accepted in comparisons. -We could have the `check-cfg` `version` predicate only apply to the `cfg` `version` predicate, +We could have the `check-cfg` `since` predicate only apply to the `cfg` `since` predicate, causing `#[cfg(rust = "1.100.0")]` to warn. However, -- the `version` predicates are a general feature intended to be used with other version numbers where exact matches may be appropriate. +- the `since` predicates are a general feature intended to be used with other version numbers where exact matches may be appropriate. - this would get in the way of approximating the vendor version by the language version for working around compiler bugs and snapshotting of compiler output. Possibly there could be a clippy lint specifically about `rust = ""`. Alternatively, we could try to find a way to structure `--check-cfg` to allow the person defining the `cfg` to decide whether it can be use with `=` or not. -One way of doing this is by allowing the `check-cfg` `version` predicate outside of the `values` predicate, -meaning it works with the `cfg` `version` predicate and not the `=` operator. -Another way would be for the `check-cfg` version predicate to never work with `=` but to instead -allow operators inside of the `cfg` `version` predicate, e.g. `#[cfg(version(rust, "=1.95.0"))]`. +One way of doing this is by allowing the `check-cfg` `since` predicate outside of the `values` predicate, +meaning it works with the `cfg` `since` predicate and not the `=` operator. +Another way would be for the `check-cfg` `since` predicate to never work with `=` but to instead +allow operators inside of the `cfg` `since` predicate, e.g. `#[cfg(since(rust, "=1.95.0"))]`. +However, with the rename of the predicate from `version` to `since`, operators don't fit in as easily. `--check-cfg` will cause the following to warn: ```rust fn is_stderr_terminal() -> bool { #[cfg(rust)] - #[cfg(version(rust, "1.70"))] + #[cfg(since(rust, "1.70"))] use std::io::IsTerminal as _; #[cfg(rust)] - #[cfg(not(version(rust, "1.70")))] + #[cfg(not(since(rust, "1.70")))] use is_terminal::IsTerminal as _; #[cfg(not(rust))] use is_terminal::IsTerminal as _; @@ -373,13 +379,13 @@ The initial implementation treated nightlies as complete. This was [changed to incomplete](https://github.com/rust-lang/rust/pull/72001) after [some discussion](https://github.com/rust-lang/rust/issues/64796#issuecomment-624673206). In particular, this is important for -- the case of package `bleeding-edge` starting to use a new feature behind `#[cfg(version)]` and package `nightly-only` has their toolchain pinned to a nightly before the feature was stabilized (to ensure consistent behavior of unstable features), package `nightly-only` cannot add or update their dependency on `bleeding-edge` without getting a "feature gate needed" error. +- the case of package `bleeding-edge` starting to use a new feature behind `#[cfg(since)]` and package `nightly-only` has their toolchain pinned to a nightly before the feature was stabilized (to ensure consistent behavior of unstable features), package `nightly-only` cannot add or update their dependency on `bleeding-edge` without getting a "feature gate needed" error. - bisecting nightlies. This was [changed back to complete](https://github.com/rust-lang/rust/pull/81468) after [some more discussion](https://github.com/rust-lang/rust/issues/64796#issuecomment-634546711). In particular, this is important for -- keeping friction down for packages preparing for stabilized-on-nightly features as their `#[cfg(version)]`s can be inserted and "just work" which can be important for getting feedback quickly while the feature is easier to adapt to feedback that can be gained from these users +- keeping friction down for packages preparing for stabilized-on-nightly features as their `#[cfg(since)]`s can be inserted and "just work" which can be important for getting feedback quickly while the feature is easier to adapt to feedback that can be gained from these users - releasing the package while its in this state puts it at risk to be broken if the feature is changed after stabilization For RFC 2523, they settled on pre-releases being incomplete, @@ -387,7 +393,7 @@ favoring maintainers to adopt stabilized-on-nightly features immediately while letting people on pinned nightlies or bisecting nightlies to set a `-Z` to mark the version as incomplete. In this RFC, we settled translating `-nightly` to `-incomplete` because: -- Maintainers can adopt stabilized-on-nightly features with `#[cfg(version(rust, "1.100.0-0"))]` (the lowest pre-release for `1.100.0`), keeping friction low while explicitly acknowledging that the unstable feature may change +- Maintainers can adopt stabilized-on-nightly features with `#[cfg(since(rust, "1.100.0-0"))]` (the lowest pre-release for `1.100.0`), keeping friction low while explicitly acknowledging that the unstable feature may change - Allows build scripts to experiment with other logic without less chance of needing to invoke `rustc` (e.g. detecting nightly) - It provides extra context when approximating the vendor version from the language version when populating build information @@ -552,12 +558,12 @@ Haskell: # Future possibilities [future-possibilities]: #future-possibilities -- In the future the `--check-cfg` `version()` predicate could make the minimum-version field optional, +- In the future the `--check-cfg` `since()` predicate could make the minimum-version field optional, matching all version numbers. ## Relaxing SemVer -Instead of requiring the `IDENTIFIER` in the `version` predicate to be strictly SemVer `major.minor.patch`, +Instead of requiring the `IDENTIFIER` in the `check-cfg` `since` predicate to be strictly SemVer `major.minor.patch`, we could allow abbreviated forms like `major.minor` or even `major`. This would make the predicate more inclusive for other cases, like `edition`. @@ -583,12 +589,12 @@ See also [`#[cfg(nightly)]`](https://rust-lang.github.io/rfcs/2523-cfg-path-vers ## `cfg_target_version` Instead of defining a new `#[cfg]` predicate, [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750) -could reuse the `#[cfg(version)]` predicate. +could reuse the `#[cfg(since)]` predicate. As not all systems use SemVer, we can either - Contort the version into SemVer - This can run into problems either with having more precision (e.g. `120.0.1.10` while SemVer only allows `X.Y.Z`) or post-release versions (e.g. `1.2.0.post1` which a SemVer predicate would treat as a pre-release). -- Add an optional third field for specifying the version format (e.g. `#[cfg(version(windows, "10.0.10240", )]`) +- Add an optional third field for specifying the version format (e.g. `#[cfg(since(windows, "10.0.10240", )]`) - Make `--check-cfg` load-bearing by having the version policy name be specified in the `--check-cfg` predicate ## Provide a way to get a `--cfg`s value @@ -601,4 +607,4 @@ this would allow an application to approximate the vendor version `--bugreport` As the ecosystem grows and matures, the Rust language and standard library may not be the only dependencies users wish to support multiple versions of. -We may want to allow `#(cfg(version(serde, "1.0.900")]`. +We may want to allow `#(cfg(since(serde, "1.0.900")]`. From 2ee371886a00695184f2c2fe48e64a5c2b5cea89 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 14:27:51 -0500 Subject: [PATCH 004/109] Rename file for RFC # --- text/{0000-cfg-version.md => 3857-cfg-version.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-cfg-version.md => 3857-cfg-version.md} (100%) diff --git a/text/0000-cfg-version.md b/text/3857-cfg-version.md similarity index 100% rename from text/0000-cfg-version.md rename to text/3857-cfg-version.md From 20233b0879e1ab8f5a980ddb77fc9f92d3476da0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 14:28:15 -0500 Subject: [PATCH 005/109] Link out to the RFC --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index f7574f079cf..cd827b1fd40 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -1,6 +1,6 @@ - Feature Name: `cfg_version` - Start Date: 2025-09-13 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3857](https://github.com/rust-lang/rfcs/pull/3857) - Rust Issue: [rust-lang/rust#64796](https://github.com/rust-lang/rust/issues/64796) # Summary From 3d827bcd900888adb2ca10cb17f6b345506b2056 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 14:38:47 -0500 Subject: [PATCH 006/109] Add another problem with vendor version --- text/3857-cfg-version.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index cd827b1fd40..decf5bc3192 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -578,6 +578,7 @@ Some challenges for this with `rustc --version`: all mapping to the `-nightly` pre-release version rather than including the date within the pre-release - This does not conform to SemVer's precedence rules, as `-nightly` is an older version than `-beta.4` while [SemVer's precedence rules](https://semver.org/#spec-item-11) say the opposite +- Crater runs and local builds don't necessarily have a version that fits within this picture ## `#[cfg(nightly)]` From a24a6a4df947b67e5f38512b281bc8b638472306 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 14:45:31 -0500 Subject: [PATCH 007/109] Fix grammar --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index decf5bc3192..87e9fcb8126 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -165,7 +165,7 @@ fn main() { // ^--- This warning only happens if we are on Rust >= 1.27. } ``` -However, this would produce a `unexpected_cfgs` lint and you would need to add the following to `Cargo.toml`: +However, this would produce an `unexpected_cfgs` lint and you would need to add the following to `Cargo.toml`: ```toml [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } From 5a7c0e9620b504b9930865bd54fb86a6c8cb77b5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 14:46:23 -0500 Subject: [PATCH 008/109] Fix grammar --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 87e9fcb8126..d43a24219d8 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -195,7 +195,7 @@ If `IDENTIFIER` has no value or is undefined, this will evaluate to `false`. If `IDENTIFIER` is not a valid [SemVer](https://semver.org/) value, this will evaluate to `false`. -If the string literal is does not conform to the syntax from `` to `..-` where the first three fields must be integers, this will evaluate to `false`. +If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, this will evaluate to `false`. Note that this excludes support for the `+build` field. Otherwise, the `IDENTIFIER` will be compared to the string literal according to From d392d5014d7ebd886316ce2d7820b84134dfa566 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 14:49:11 -0500 Subject: [PATCH 009/109] Clean up a sentence --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index d43a24219d8..d0405899651 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -237,7 +237,7 @@ So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, A new built-in cfg `--cfg rust=` will be added by the compiler that specifies the language version. -This will be the version of `rustc` with how the pre-release version is handled being unspecified. +This will be the version of `rustc` with the behavior for pre-release versions being unspecified. We expect rustc to: - Translate the `-nightly` pre-release to `-incomplete - Strip the `-beta.5` pre-release From e51caf28dda2c5da134b60dee7532fd262241b17 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 14:50:33 -0500 Subject: [PATCH 010/109] Remove behavior left behind from a previous iteration --- text/3857-cfg-version.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index d0405899651..186ba0949b3 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -238,9 +238,7 @@ So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, A new built-in cfg `--cfg rust=` will be added by the compiler that specifies the language version. This will be the version of `rustc` with the behavior for pre-release versions being unspecified. -We expect rustc to: -- Translate the `-nightly` pre-release to `-incomplete -- Strip the `-beta.5` pre-release +We expect rustc to strip pre-release versions, treating them as if they fully implement that language version. `rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95.0")))'` (or whatever version this gets stabilized in). From cb8acba0f5c198a7b730e7635669f14c9012540a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:04:44 -0500 Subject: [PATCH 011/109] Revert "Remove behavior left behind from a previous iteration" This reverts commit e51caf28dda2c5da134b60dee7532fd262241b17. --- text/3857-cfg-version.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 186ba0949b3..d0405899651 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -238,7 +238,9 @@ So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, A new built-in cfg `--cfg rust=` will be added by the compiler that specifies the language version. This will be the version of `rustc` with the behavior for pre-release versions being unspecified. -We expect rustc to strip pre-release versions, treating them as if they fully implement that language version. +We expect rustc to: +- Translate the `-nightly` pre-release to `-incomplete +- Strip the `-beta.5` pre-release `rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95.0")))'` (or whatever version this gets stabilized in). From a1a81640bfb326f0b06a4769f40bdc2db42670a3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:06:45 -0500 Subject: [PATCH 012/109] Update Guide to reflect how nightlies are proposed I forgot where I landed on how to handle them --- text/3857-cfg-version.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index d0405899651..e193a13a05a 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -171,12 +171,20 @@ However, this would produce an `unexpected_cfgs` lint and you would need to add unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } ``` -Say you were wanting to test out `#[must_use]` after it got stabilized on nightly to provide feedback and to be ready for when it hits stable, there would be no change to the examples above. -Nightly compiler releases report themselves as the version they will be on hitting the stable channel. +Say you were wanting to test out `#[must_use]` after it got stabilized on nightly to provide feedback and to be ready for when it hits stable, +you would instead use `"1.27.0-0"` to match all pre-release versions of 1.27.0: +```rust +#[cfg_attr(since(rust, "1.27.0-0"), must_use)] +fn double(x: i32) -> i32 { + 2 * x +} -On the flip side, you might be bisecting nightlies or pinning to a 1.27 nightly before `#[must_use]` was stabilized. -You would have the sad predicament of not being able to use `double` because you would get a feature-gate error. -In that case, you can pass `-Z assume-incomplete-release` to the compiler so that 1.27 nightly reports itself as being 1.26 and `#[must_use]` won't be applied within `double`. +fn main() { + double(4); + // warning: unused return value of `double` which must be used + // ^--- This warning only happens if we are on Rust >= 1.27. +} +``` # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From c5e9d5f028289436f9fb608bdcdb4364ce967417 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:09:05 -0500 Subject: [PATCH 013/109] Misc cleanup --- text/3857-cfg-version.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index e193a13a05a..b34e81647e1 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -258,7 +258,7 @@ This will be reported back through `--print-cfg`. Because this gets reported back in `--print-cfg`, Cargo will expose `rust` in: - build scripts as `CARGO_CFG_RUST` -- `target."cfg()".dependencies` +- `[target."cfg()".dependencies]` ## clippy @@ -268,7 +268,7 @@ However, it will be perfectly reasonable to use those items when guarded by a `# Clippy may wish to: - Find a way to reduce false positives, e.g. evaluating the `cfg(since)`s that led to the item's usage or disabling the lint within `#[cfg(since)]` -- Suggest `#[cfg(since)]` in the `clippy::incompatible_msrv` diagnostic report (maybe along with offering to bump MSRV) +- Suggest `#[cfg(since)]` in the `clippy::incompatible_msrv` diagnostic report (maybe along with offering to bump MSRV as that is a reasonable alternative) # Drawbacks [drawbacks]: #drawbacks @@ -292,7 +292,7 @@ Libraries could having ticking time bombs that accidentally break or have undesi ## `since` cfg predicate -We could offer a `before` operator but that is already covered by `not(since)`. +We could offer a `before` predicate but that is already covered by `not(since)`. The `since` name was taken from [rustversion](https://crates.io/crates/rustversion) and the `#[deprecated(since)]` / `#[stable(since)]` attributes. @@ -331,11 +331,11 @@ in case we want the future possibility of relaxing SemVer versions We could have the `check-cfg` `since` predicate only apply to the `cfg` `since` predicate, causing `#[cfg(rust = "1.100.0")]` to warn. However, -- the `since` predicates are a general feature intended to be used with other version numbers where exact matches may be appropriate. +- the `since` predicates are a general feature intended to be used with other version numbers where exact matches may also be appropriate. - this would get in the way of approximating the vendor version by the language version for working around compiler bugs and snapshotting of compiler output. Possibly there could be a clippy lint specifically about `rust = ""`. -Alternatively, we could try to find a way to structure `--check-cfg` to allow the person defining the `cfg` to decide whether it can be use with `=` or not. +Alternatively, we could try to find a way to structure `--check-cfg` to allow the person defining the `cfg` to decide whether it can be used with `=` or not. One way of doing this is by allowing the `check-cfg` `since` predicate outside of the `values` predicate, meaning it works with the `cfg` `since` predicate and not the `=` operator. Another way would be for the `check-cfg` `since` predicate to never work with `=` but to instead @@ -363,7 +363,7 @@ To allow checking for the presence of `rust`, add the following to your `Cargo.t [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } ``` -Alternatively, we could have the built-in `--check-cfg` include `values(none())` but: +Alternatively, we could have the built-in `--check-cfg` for `rust` include `values(none())` but: - When building on an old version, users will see the warning and will likely want to add it anyways. - We lose out on `--check-cfg` identifying misused. Instead, we may wish to add a dedicated predicate intended for "is set". @@ -373,12 +373,16 @@ Alternatively, we could have the built-in `--check-cfg` include `values(none())` While there was concern over `rust` appearing in the name of `cfg(rust_version("1.95"))`, I feel that `rust` as its own entity makes sense and avoids that problem. +Rust does appear in some parts of the language, +but is capitalized like with [`repr(Rust)`](https://doc.rust-lang.org/reference/type-layout.html?#the-rust-representation). +However, the convention for `--cfg`s is generally lower case. + ### Pre-release -When translating `rustc --version` to a Language version, we have several choices when it comes to pre-releases, including: -- Treat the nightly as fully implementing that Language version -- Treat the nightly as not implementing that Language version at all, only the previous -- Leave a marker that that Language version is incomplete, while the previous Language version is complete +When translating `rustc --version` to a language version, we have several choices when it comes to pre-releases, including: +- Treat the nightly as fully implementing that language version +- Treat the nightly as not implementing that language version at all, only the previous +- Leave a marker that that language version is incomplete, while the previous language version is complete In RFC 2523, this was left as an [unresolved question](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html#unresolved-questions). @@ -400,7 +404,7 @@ For RFC 2523, they settled on pre-releases being incomplete, favoring maintainers to adopt stabilized-on-nightly features immediately while letting people on pinned nightlies or bisecting nightlies to set a `-Z` to mark the version as incomplete. -In this RFC, we settled translating `-nightly` to `-incomplete` because: +In this RFC, we settled on translating `-nightly` to `-incomplete` because: - Maintainers can adopt stabilized-on-nightly features with `#[cfg(since(rust, "1.100.0-0"))]` (the lowest pre-release for `1.100.0`), keeping friction low while explicitly acknowledging that the unstable feature may change - Allows build scripts to experiment with other logic without less chance of needing to invoke `rustc` (e.g. detecting nightly) - It provides extra context when approximating the vendor version from the language version when populating build information From 465cecc0032a48732e0222795031b0925dae79f5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:16:35 -0500 Subject: [PATCH 014/109] Update text/3857-cfg-version.md Co-authored-by: Jubilee --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index b34e81647e1..53c5ddfaa93 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -435,7 +435,7 @@ On the [stabilization issue](https://github.com/rust-lang/rust/pull/141766), there was concern about the name "rust" in this predicate not fitting in with the rest of the language. However, dropping it to `version` would make things awkward in Cargo where there wouldn't be enough context for which item's `version` is being referred to. There is also a future possibility of better integrating dependency versions into the language. -If done, then `version` may be come more ambiguous even in Rust. +If done, then `version` may become more ambiguous even in Rust. For example, if Cargo told rustc the minimum compatible version for a dependency, `#[deprecated(since)]`` warnings could not emit if the minimum version bound is lower than `since`. Similarly, if we stabilized `#[stable(since)]`, a linter could report when a version requirement is too low. From 0fed55361431a50c79d507afeb23511bf7a21c24 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:18:19 -0500 Subject: [PATCH 015/109] Fix grammar --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 53c5ddfaa93..e9ed7359f3f 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -406,7 +406,7 @@ while letting people on pinned nightlies or bisecting nightlies to set a `-Z` to In this RFC, we settled on translating `-nightly` to `-incomplete` because: - Maintainers can adopt stabilized-on-nightly features with `#[cfg(since(rust, "1.100.0-0"))]` (the lowest pre-release for `1.100.0`), keeping friction low while explicitly acknowledging that the unstable feature may change -- Allows build scripts to experiment with other logic without less chance of needing to invoke `rustc` (e.g. detecting nightly) +- Allows build scripts to experiment with other logic when approximating the vendor version from the language version with less of a chance of needing to invoke `rustc` (e.g. detecting nightly) - It provides extra context when approximating the vendor version from the language version when populating build information We called the pre-release `-incomplete` to speak to the relationship to the language version. From 3a7609a44776c33d1a4f99396a5dfb694d5f9482 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:22:29 -0500 Subject: [PATCH 016/109] Unquoted versions may negstively interact with target_version --- text/3857-cfg-version.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index e9ed7359f3f..01452f0a940 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -314,6 +314,8 @@ If we were stricter on the syntax, we could allow for version numbers to be directly accepted, without quotes (e.g. `#[cfg(since(rust, 1.95.0))]`). If we ever decided to support operators (e.g.`#[cfg(since(rust, "=1.95.0"))]`, see `--check-cfg`), then we'd need to decide if those also go outside the string or then require a string, being inconsistent. +This may limit us if we decide to allow for alternative version formats like with [target_version](#cfg_target_version) as they may not have formats that map well to SemVer. +Worst case, we'd need to accept arbitrary bare words. This would also be inconsistent with other uses of `cfg`s *but* maybe that would just be the start to natively supporting more types in `cfg`, like integers which are of interest to embedded folks. From 55a7632a05235f42b05e25018793c28f92d24a22 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:30:30 -0500 Subject: [PATCH 017/109] Fix unclosed literal Co-authored-by: bjorn3 <17426603+bjorn3@users.noreply.github.com> --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 01452f0a940..d4acf970a7a 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -247,7 +247,7 @@ A new built-in cfg `--cfg rust=` will be added by the compiler that specifies the language version. This will be the version of `rustc` with the behavior for pre-release versions being unspecified. We expect rustc to: -- Translate the `-nightly` pre-release to `-incomplete +- Translate the `-nightly` pre-release to `-incomplete` - Strip the `-beta.5` pre-release `rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95.0")))'` From 545ef5257edcfb0d41c62bb8498c4fac6b4087d2 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:50:07 -0500 Subject: [PATCH 018/109] Add counter balance to build probe drawback --- text/3857-cfg-version.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index d4acf970a7a..103e04d1c70 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -283,7 +283,16 @@ While that isn't always true today (e.g. some Cargo features go from "unknown" w having distinct implementations for different Rust versions can make the testing matrix more complex. Tools like [`cargo hack`](https://crates.io/crates/cargo-hack) can help which can run commands on not just one toolchain version but also the every version starting with the MSRV with a command like `cargo hack --rust-version --version-step 1 check`. -This does not help with probes for specific implementations of nightly features which still requires having a `build.rs`. +This does not help with identifying nightlies that a feature is available on or compatible with which will still require a `build.rs`. +In terms of doing this via build probes, +Cargo team has previously rejected support for build probes +([source](https://github.com/rust-lang/cargo/issues/11244#issuecomment-2326780810)). +Whether build probes or nightly version checks, +auto-enabling nightly features +(rather than having users opt-in) +runs counter to the spirit of nightly (works like stable except where you opt-in) +and cause problems if the checks are incorrect which has broken many crates nightly builds in the past +and has even caused friction in changing unstable features within the compiler. Libraries could having ticking time bombs that accidentally break or have undesired behavior for some future Rust version that can't be found until we hit that version. From 4352a955d7936d478fa4f4d533fad3b87caa4b4d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:54:15 -0500 Subject: [PATCH 019/109] Clarify Python's post-release version comment --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 103e04d1c70..48cd4aa155b 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -617,7 +617,7 @@ could reuse the `#[cfg(since)]` predicate. As not all systems use SemVer, we can either - Contort the version into SemVer - - This can run into problems either with having more precision (e.g. `120.0.1.10` while SemVer only allows `X.Y.Z`) or post-release versions (e.g. `1.2.0.post1` which a SemVer predicate would treat as a pre-release). + - This can run into problems either with having more precision (e.g. `120.0.1.10` while SemVer only allows `X.Y.Z`) or post-release versions (e.g. [`1.2.0.post1`](https://packaging.python.org/en/latest/discussions/versioning/) which, if we translated it to SemVer's syntax of `1.2.0-post1`, would be treated as a pre-release). - Add an optional third field for specifying the version format (e.g. `#[cfg(since(windows, "10.0.10240", )]`) - Make `--check-cfg` load-bearing by having the version policy name be specified in the `--check-cfg` predicate From c0a7dc4d842cb3a233c11abec066f210a8a5dc61 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 15:55:02 -0500 Subject: [PATCH 020/109] Fix grammar Co-authored-by: Ralf Jung --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 48cd4aa155b..9a683165298 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -291,7 +291,7 @@ Whether build probes or nightly version checks, auto-enabling nightly features (rather than having users opt-in) runs counter to the spirit of nightly (works like stable except where you opt-in) -and cause problems if the checks are incorrect which has broken many crates nightly builds in the past +and cause problems if the checks are incorrect which has broken many crates' nightly builds in the past and has even caused friction in changing unstable features within the compiler. Libraries could having ticking time bombs that accidentally break or have undesired behavior for some future Rust version that can't be found until we hit that version. From ce778582da0f39874ba323e052fdefea4a0ffed6 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 16:01:27 -0500 Subject: [PATCH 021/109] Fix print=cfg syntax Co-authored-by: Urgau <3616612+Urgau@users.noreply.github.com> --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 9a683165298..2ce4aaf7f4c 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -253,7 +253,7 @@ We expect rustc to: `rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95.0")))'` (or whatever version this gets stabilized in). -This will be reported back through `--print-cfg`. +This will be reported back through `--print=cfg`. Because this gets reported back in `--print-cfg`, Cargo will expose `rust` in: From 7ba5578948f97f78309aa7fccec80150598972af Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 16:01:36 -0500 Subject: [PATCH 022/109] Fix print=cfg syntax Co-authored-by: Urgau <3616612+Urgau@users.noreply.github.com> --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 2ce4aaf7f4c..ad72b88309f 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -255,7 +255,7 @@ We expect rustc to: This will be reported back through `--print=cfg`. -Because this gets reported back in `--print-cfg`, +Because this gets reported back in `--print=cfg`, Cargo will expose `rust` in: - build scripts as `CARGO_CFG_RUST` - `[target."cfg()".dependencies]` From 0d7f46ce8e4cb868aca0a240ba6ade0e17fb71be Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 16:05:52 -0500 Subject: [PATCH 023/109] Clarify check-cfg --- text/3857-cfg-version.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index ad72b88309f..4898e87a65d 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -221,13 +221,13 @@ ConfigurationVersion -> `since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` The syntax for the contents of the string literal is a SemVer value without the build field. -This will specify that the given `--cfg` is valid for all values that match: +This will specify that for the given cfg, values will be valid if: - SemVer syntax - from the specified version and up -When the given `--cfg` is used with the `since` predicate: +Specifically when the given cfg is used with the `cfg` `since` predicate: - the string literal should not be of a syntax that evaluates to `false` -- the minimum version requirement must specify a subset of what the `--check-cfg` specifies +- the string literal must be a minimum version requirement that specifies a subset of what the `--check-cfg` specifies So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, - ✅ `#[cfg(foo = "1.100.0")]` From b2e8cdb35b2179345ff9fbf26d902398bec9ae12 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Sep 2025 16:12:39 -0500 Subject: [PATCH 024/109] Clarify the alternatives for check-cfg --- text/3857-cfg-version.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 4898e87a65d..c4460bdfc68 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -346,12 +346,13 @@ However, - this would get in the way of approximating the vendor version by the language version for working around compiler bugs and snapshotting of compiler output. Possibly there could be a clippy lint specifically about `rust = ""`. -Alternatively, we could try to find a way to structure `--check-cfg` to allow the person defining the `cfg` to decide whether it can be used with `=` or not. +Alternatively, we could try to find a way to structure `--check-cfg` to allow the person setting the `check-cfg` to decide whether it can be used with `=` or not. One way of doing this is by allowing the `check-cfg` `since` predicate outside of the `values` predicate, meaning it works with the `cfg` `since` predicate and not the `=` operator. Another way would be for the `check-cfg` `since` predicate to never work with `=` but to instead allow operators inside of the `cfg` `since` predicate, e.g. `#[cfg(since(rust, "=1.95.0"))]`. However, with the rename of the predicate from `version` to `since`, operators don't fit in as easily. +If someone wanted to support equality checks, there wouldn't be a way to support a continuous range of `values()` but would instead have to manually specify each likely potential version. `--check-cfg` will cause the following to warn: ```rust From 50e694963464886bde1456df8dc2afa92fea81f1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 16 Sep 2025 15:56:37 -0500 Subject: [PATCH 025/109] Provide unique anchors --- text/3857-cfg-version.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index c4460bdfc68..ddbcba0ebd6 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -299,7 +299,7 @@ Libraries could having ticking time bombs that accidentally break or have undesi # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -## `since` cfg predicate +## `since` cfg predicate rationale We could offer a `before` predicate but that is already covered by `not(since)`. @@ -329,7 +329,7 @@ This would also be inconsistent with other uses of `cfg`s *but* maybe that would just be the start to natively supporting more types in `cfg`, like integers which are of interest to embedded folks. -## `--check-cfg` +## `--check-cfg` rationale The `--check-cfg` predicate and the value for `rust` ensures users get warnings about - Invalid syntax @@ -380,7 +380,7 @@ Alternatively, we could have the built-in `--check-cfg` for `rust` include `valu - We lose out on `--check-cfg` identifying misused. Instead, we may wish to add a dedicated predicate intended for "is set". -## `rust` cfg +## `rust` cfg rationale While there was concern over `rust` appearing in the name of `cfg(rust_version("1.95"))`, I feel that `rust` as its own entity makes sense and avoids that problem. From 2348d45ec01103a4fc17fc481ad58189121c5fad Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 16 Sep 2025 15:58:38 -0500 Subject: [PATCH 026/109] Future possibility for check-cfg version without a minimum --- text/3857-cfg-version.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index ddbcba0ebd6..1f6d10e68c1 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -633,3 +633,13 @@ this would allow an application to approximate the vendor version `--bugreport` As the ecosystem grows and matures, the Rust language and standard library may not be the only dependencies users wish to support multiple versions of. We may want to allow `#(cfg(since(serde, "1.0.900")]`. + +## `check-cfg` support for a version without a minimum + +`--check-cfg 'cfg(foo, values(since("1.95.0")))'` requires setting a minimum version. +If a user did not need that when setting a `cfg`, +they would have to do `--check-cfg 'cfg(foo, values(since("0.0.0-0")))'`. +A user may want a shorthand for this. +With the name `since`, defaulting it to `"0.0.0-0"` doesn't read too well (--check-cfg 'cfg(foo, values(since()))'`). +Maybe a new predicate can be added `version()`. +A shorthand may be limited to SemVer versions if we use the `since(version)` syntax to specify the supported version syntax, see [`--check-cfg` rationale][#--check-cfg-rationale]. From 6f656d541455b754d0a1076ce03da20ea19cee1f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 16 Sep 2025 16:33:06 -0500 Subject: [PATCH 027/109] fix(drawback): Simplify nightly discussion In part, this reverts 545ef5257edcfb0d41c62bb8498c4fac6b4087d2. I don't want to get into an argument over the right way we should be discouraging nightly shenanigans or how much of a use case there is or not. That is all for future possibilities. The main point is that we aren't covering a use case from one piece of prior art. --- text/3857-cfg-version.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 1f6d10e68c1..d25cf2eda0d 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -283,16 +283,9 @@ While that isn't always true today (e.g. some Cargo features go from "unknown" w having distinct implementations for different Rust versions can make the testing matrix more complex. Tools like [`cargo hack`](https://crates.io/crates/cargo-hack) can help which can run commands on not just one toolchain version but also the every version starting with the MSRV with a command like `cargo hack --rust-version --version-step 1 check`. -This does not help with identifying nightlies that a feature is available on or compatible with which will still require a `build.rs`. -In terms of doing this via build probes, -Cargo team has previously rejected support for build probes -([source](https://github.com/rust-lang/cargo/issues/11244#issuecomment-2326780810)). -Whether build probes or nightly version checks, -auto-enabling nightly features -(rather than having users opt-in) -runs counter to the spirit of nightly (works like stable except where you opt-in) -and cause problems if the checks are incorrect which has broken many crates' nightly builds in the past -and has even caused friction in changing unstable features within the compiler. +As we don't expose a nightly's date, +this does not cover the use case from [rustversion](https://crates.io/crates/rustversion) represented by +`#[rustversion::since(2025-01-01)]`. Libraries could having ticking time bombs that accidentally break or have undesired behavior for some future Rust version that can't be found until we hit that version. From 21b8dc012cc729fbcc9b6e2fc5c28fbf690d5946 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 16 Sep 2025 16:39:02 -0500 Subject: [PATCH 028/109] fix(ref): Draw more attention to the warnings when discussion cfg --- text/3857-cfg-version.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index d25cf2eda0d..96903050b4b 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -203,6 +203,8 @@ If `IDENTIFIER` has no value or is undefined, this will evaluate to `false`. If `IDENTIFIER` is not a valid [SemVer](https://semver.org/) value, this will evaluate to `false`. +*(for warning on always-false checks, see `--check-cfg`)* + If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, this will evaluate to `false`. Note that this excludes support for the `+build` field. @@ -210,8 +212,6 @@ Otherwise, the `IDENTIFIER` will be compared to the string literal according to [Cargo's `>=` version requirements](https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#comparison-requirements). For example, `#[cfg(since(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. -See also `--check-cfg`. - ## `--check-cfg` A new predicate will be added of the form: From ee1c8cfc875319615eaff34abb20d46d66c8f6a9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 16 Sep 2025 16:53:26 -0500 Subject: [PATCH 029/109] fix(guide): Fix bad versions and try to clarify example --- text/3857-cfg-version.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 96903050b4b..6fe98bde4ea 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -121,7 +121,13 @@ circumstances may warrant doing so while maintaining an existing [MSRV](https:// rather than raising to what the language or standard library feature needs. This can be accomplished by conditionally compiling the code for that feature. -For instance, say you have an MSRV of 1.10 and `#[cfg(since)]` feature was available in 1.20, you would have been able to do: +As its hard to talk about features and versions in the future, +we're going to step through this in an alternate reality where: +- `--check-cfg` (warn on invalid conditional compilation) was stabilized in 1.0 +- `--cfg rust` and `#[cfg(since)]` were stabilized in 1.20 +- `#[must_use]` (an example language feature) was still stabilized in 1.27 + +For instance, say you have an MSRV of 1.20, to use `#[must_use]` you would do: ```rust #[cfg_attr(since(rust, "1.27"), must_use)] fn double(x: i32) -> i32 { @@ -152,7 +158,11 @@ fn main() { > } > ``` -Now, let's say `#[cfg(since)]` was stabilized in Rust 1.27 or later, you can check for support for it with: +Now, +let's say your MSRV is 1.10, +the above code would error when compiling with your MSRV because the `since` predicate does not exist with that version. +However, the presence of `--cfg rust` implies that we are on 1.27, +so you can "detect" support for `since` by changing your code to: ```rust #[cfg_attr(rust, cfg_attr(since(rust, "1.27"), must_use))] fn double(x: i32) -> i32 { From ec2e54c99042b4d189bade7d880ca877fd072c25 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 11:50:44 -0500 Subject: [PATCH 030/109] feat(rationale): Cover before --- text/3857-cfg-version.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 6fe98bde4ea..8f5938ed0b0 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -286,6 +286,10 @@ Clippy may wish to: People may be using `--cfg rust` already and would be broken by this change. There are no compatibility concerns with predicate names. +`#[cfg(not(since("1.95.0")))]` is unnatural grammar when read out loud and could cause confusion. +This could be helped by supporting a `#[cfg(before("1.95.0"))]`. +This was left to [a future possibility][future-possibilities]. + This does not include a solution for adopting this within `Cargo.toml` without waiting for an MSRV bump. Traditionally, maintainers only test their MSRV and latest, assuming those will catch every issue. @@ -304,8 +308,6 @@ Libraries could having ticking time bombs that accidentally break or have undesi ## `since` cfg predicate rationale -We could offer a `before` predicate but that is already covered by `not(since)`. - The `since` name was taken from [rustversion](https://crates.io/crates/rustversion) and the `#[deprecated(since)]` / `#[stable(since)]` attributes. This better conveys what operation is being performed than the original `version` name @@ -587,6 +589,8 @@ Haskell: - In the future the `--check-cfg` `since()` predicate could make the minimum-version field optional, matching all version numbers. +- Adding `#[cfg(before("1.95.0"))]` could resolve the unnatural grammar of `#[cfg(not(since("1.95.0")))]`. + - Deferring to keep this minimal and to get more real world input on the usefulness of this ## Relaxing SemVer From 96ffa1596174a83a85060ac293b8125b74933a6b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 11:51:18 -0500 Subject: [PATCH 031/109] feat(drawbacks): Cover pre-releases of major versions --- text/3857-cfg-version.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 8f5938ed0b0..a181bf9a58e 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -303,6 +303,27 @@ this does not cover the use case from [rustversion](https://crates.io/crates/rus Libraries could having ticking time bombs that accidentally break or have undesired behavior for some future Rust version that can't be found until we hit that version. +## Pre-releases for major versions + +Pre-releases of major versions isn't a consideration for `rust` but in the general use of `since`. + +If wanting to split a continuous range with minor and patch versions, +`#[cfg(since(foo, "1.1.0"))]` and `#[cfg(not(since(foo, "1.1.0")))]` +works reasonably well. + +The problem comes into play when doing so with major versions when pre-releases are involved, +like `#[cfg(since(foo, "2.0.0"))]` and `#[cfg(not(since(foo, "2.0.0")))]`. +In this situation, a `2.0.0-dev.5` will match the second condition when the user likely only wanted to include `1.*`. +Instead, they should do `#[cfg(since(foo, "2.0.0-0"))]` and `#[cfg(not(since(foo, "2.0.0-0")))]` or have a third case for pre-releases of `foo@2.0.0`. + +This came up in Cargo when considering how to improve interactions with pre-releases. +Cargo has the advantages of: +- Not working with splitting continuous ranges, so special cases can be made that cause discontinuities +- Simpler expressions that can be analyzed for considering global knowledge. + +For more information on Cargo's experiments with this (all unstable), +see [cargo#14305](https://github.com/rust-lang/cargo/pull/14305). + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From a07e1c693a96726a24f0ea6646725b6613d9b984 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 11:57:29 -0500 Subject: [PATCH 032/109] fix(summary): Be name the predicate being added --- text/3857-cfg-version.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index a181bf9a58e..76e44ba0e11 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -6,7 +6,9 @@ # Summary [summary]: #summary -Allow Rust-version conditional compilation by adding a built-in `--cfg rust=` and a minimum-version `#[cfg]` predicate. +Allow Rust-version conditional compilation by adding +- a built-in `--cfg rust=`, for the Rust language version +- `#[cfg(since(cfg_name, ""))]`, a minimum-version `cfg` predicate Say this was added before 1.70, you could do: ```toml From c3867009fe1b9d74ba7b79730edc4e94cc66c1ab Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 12:00:10 -0500 Subject: [PATCH 033/109] fix(drawbacks): Tweak language around cargo requiring msrv bump for since --- text/3857-cfg-version.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 76e44ba0e11..a5094b2c50e 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -292,7 +292,8 @@ There are no compatibility concerns with predicate names. This could be helped by supporting a `#[cfg(before("1.95.0"))]`. This was left to [a future possibility][future-possibilities]. -This does not include a solution for adopting this within `Cargo.toml` without waiting for an MSRV bump. +While Rust can stacks `cfg`s to test for the presence of this feature on older version, +this does not include a solution for adopting this within `Cargo.toml` without waiting for an MSRV bump to the version `since` is stabilized in. Traditionally, maintainers only test their MSRV and latest, assuming those will catch every issue. While that isn't always true today (e.g. some Cargo features go from "unknown" warning to "unstable" error to supported and MSRV might be in the warning phase), From 741f854d1d307fcede3b9c40c84b22fa2fef625a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 12:03:15 -0500 Subject: [PATCH 034/109] fix(drawback): Be explicit on why -0 is chosen for the recommendation for matches --- text/3857-cfg-version.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index a5094b2c50e..fad87e118dc 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -447,6 +447,7 @@ while letting people on pinned nightlies or bisecting nightlies to set a `-Z` to In this RFC, we settled on translating `-nightly` to `-incomplete` because: - Maintainers can adopt stabilized-on-nightly features with `#[cfg(since(rust, "1.100.0-0"))]` (the lowest pre-release for `1.100.0`), keeping friction low while explicitly acknowledging that the unstable feature may change + - `-0` is recommended over `-incomplete` or any other value as the exact pre-release value is unspecified. - Allows build scripts to experiment with other logic when approximating the vendor version from the language version with less of a chance of needing to invoke `rustc` (e.g. detecting nightly) - It provides extra context when approximating the vendor version from the language version when populating build information From 5ee6b1e8ce43c5f9bde79341f906734a7d1f0569 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 12:05:09 -0500 Subject: [PATCH 035/109] feat(prior): Include swift Co-authored-by: Mads Marquart --- text/3857-cfg-version.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index fad87e118dc..eac89c194e5 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -591,6 +591,9 @@ Accessible ## Other +Swift: +- Similar syntax, an attribute [`@available`](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes#available) with name/value pairs. Examples: `@available(swift 3.0.2)`, `@available(iOS 10.0, macOS 10.12)`. + Python - Programmatic version: [`sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info) - Vendor display version: [`sys.version`](https://docs.python.org/3/library/sys.html#sys.version) From 95c29989c6fa827fa023fee17a14c7429bbde2f3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 12:33:45 -0500 Subject: [PATCH 036/109] feat(ref): Discuss usability improvement to check-cfg warning --- text/3857-cfg-version.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index eac89c194e5..fa8719f3183 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -264,6 +264,8 @@ We expect rustc to: `rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95.0")))'` (or whatever version this gets stabilized in). +Like with `--check-cfg` for Cargo `features`, +the compiler may choose to add additional context for why this lower bound is present (not stabilized). This will be reported back through `--print=cfg`. From 1b8355ad7c8ad3f201c148a1b9be32954c71a2c3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 12:41:06 -0500 Subject: [PATCH 037/109] refactor(future): Group related items --- text/3857-cfg-version.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index fa8719f3183..0d210885310 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -628,6 +628,23 @@ Instead of requiring the `IDENTIFIER` in the `check-cfg` `since` predicate to be we could allow abbreviated forms like `major.minor` or even `major`. This would make the predicate more inclusive for other cases, like `edition`. +## `cfg_target_version` + +Instead of defining a new `#[cfg]` predicate, [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750) +could reuse the `#[cfg(since)]` predicate. + +As not all systems use SemVer, we can either +- Contort the version into SemVer + - This can run into problems either with having more precision (e.g. `120.0.1.10` while SemVer only allows `X.Y.Z`) or post-release versions (e.g. [`1.2.0.post1`](https://packaging.python.org/en/latest/discussions/versioning/) which, if we translated it to SemVer's syntax of `1.2.0-post1`, would be treated as a pre-release). +- Add an optional third field for specifying the version format (e.g. `#[cfg(since(windows, "10.0.10240", )]`) +- Make `--check-cfg` load-bearing by having the version policy name be specified in the `--check-cfg` predicate + +## Conditional compilation for dependency versions + +As the ecosystem grows and matures, +the Rust language and standard library may not be the only dependencies users wish to support multiple versions of. +We may want to allow `#(cfg(since(serde, "1.0.900")]`. + ## Vendor name and version We could add `--cfg`s for the compiler vendor name and version. @@ -648,29 +665,12 @@ we either need the language version or the vendor name and version as well as a See also [`#[cfg(nightly)]`](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html#cfgnightly) in the previous RFC. -## `cfg_target_version` - -Instead of defining a new `#[cfg]` predicate, [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750) -could reuse the `#[cfg(since)]` predicate. - -As not all systems use SemVer, we can either -- Contort the version into SemVer - - This can run into problems either with having more precision (e.g. `120.0.1.10` while SemVer only allows `X.Y.Z`) or post-release versions (e.g. [`1.2.0.post1`](https://packaging.python.org/en/latest/discussions/versioning/) which, if we translated it to SemVer's syntax of `1.2.0-post1`, would be treated as a pre-release). -- Add an optional third field for specifying the version format (e.g. `#[cfg(since(windows, "10.0.10240", )]`) -- Make `--check-cfg` load-bearing by having the version policy name be specified in the `--check-cfg` predicate - ## Provide a way to get a `--cfg`s value Similar to how `cfg!` allows doing conditionals in Rust code, provide a "`cfg_value!`" for reading the value. On top of [other use cases](https://internals.rust-lang.org/t/pre-rfc-mutually-excusive-global-features/19618) for `cfg_value!`, this would allow an application to approximate the vendor version `--bugreport` / `-v --version` without a build script. -## Conditional compilation for dependency versions - -As the ecosystem grows and matures, -the Rust language and standard library may not be the only dependencies users wish to support multiple versions of. -We may want to allow `#(cfg(since(serde, "1.0.900")]`. - ## `check-cfg` support for a version without a minimum `--check-cfg 'cfg(foo, values(since("1.95.0")))'` requires setting a minimum version. From 0ab2847ccedf13e61e4b8594904a843c4240eea8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 12:46:56 -0500 Subject: [PATCH 038/109] fix: Update ConfigurationVersion for the rename to 'since' --- text/3857-cfg-version.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 0d210885310..6358990b666 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -208,7 +208,7 @@ As Cargo mirrors Rust's `#[cfg]` syntax, it too will gain this predicate. The [syntax](https://doc.rust-lang.org/reference/conditional-compilation.html#grammar-ConfigurationPredicate) is: ``` -ConfigurationVersion -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` +ConfigurationSince -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` ``` If `IDENTIFIER` has no value or is undefined, this will evaluate to `false`. @@ -228,7 +228,7 @@ For example, `#[cfg(since(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0` A new predicate will be added of the form: ``` -ConfigurationVersion -> `since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` +CheckConfigurationSince -> `since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` ``` The syntax for the contents of the string literal is a SemVer value without the build field. @@ -346,7 +346,7 @@ While having a specific name avoids these concerns. We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(since("1.95"))]` as a shorthand. However, this would look confusing in Cargo and doesn't seem like its offering enough of a benefit to be worth the costs. -The `ConfigurationVersion` is sloppy with the string literal's syntax (relying on `--check-cfg`) so that +The `ConfigurationSince` is sloppy with the string literal's syntax (relying on `--check-cfg`) so that - Allows evolution without requiring an MSRV bump - Its consistent with other predicates, e.g. `#[cfg(foo = "1.0")]` From f1dedc59b6772b42946cc18386e12a01e35264eb Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 13:51:41 -0500 Subject: [PATCH 039/109] feat(future): Add an is_set predicate which came up while working on this RFC --- text/3857-cfg-version.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 6358990b666..13a53ff5be4 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -680,3 +680,10 @@ A user may want a shorthand for this. With the name `since`, defaulting it to `"0.0.0-0"` doesn't read too well (--check-cfg 'cfg(foo, values(since()))'`). Maybe a new predicate can be added `version()`. A shorthand may be limited to SemVer versions if we use the `since(version)` syntax to specify the supported version syntax, see [`--check-cfg` rationale][#--check-cfg-rationale]. + +## An `is_set` predicate + +There isn't a way to check if a `cfg` name is set, whether with or without values +which would work like a `cfg` version of +[`cfg_accessible`](https://dev-doc.rust-lang.org/stable/unstable-book/library-features/cfg-accessible.html) +so long as the `cfg` is unconditionally set. From 7139923e9ab594bd2aafc83b91cab7a5175b6a1c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 14:20:02 -0500 Subject: [PATCH 040/109] fix: Introduce has_rust to replace incorrect use of cfg(rust) --- text/3857-cfg-version.md | 51 +++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 13a53ff5be4..cbc691e1629 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -7,8 +7,9 @@ [summary]: #summary Allow Rust-version conditional compilation by adding -- a built-in `--cfg rust=`, for the Rust language version - `#[cfg(since(cfg_name, ""))]`, a minimum-version `cfg` predicate +- a built-in `--cfg rust=`, for the Rust language version +- a built-in `--cfg has_rust`, for detecting the presence of the above Say this was added before 1.70, you could do: ```toml @@ -163,10 +164,10 @@ fn main() { Now, let's say your MSRV is 1.10, the above code would error when compiling with your MSRV because the `since` predicate does not exist with that version. -However, the presence of `--cfg rust` implies that we are on 1.27, +However, the presence of `--cfg has_rust` implies that we are on 1.27, so you can "detect" support for `since` by changing your code to: ```rust -#[cfg_attr(rust, cfg_attr(since(rust, "1.27"), must_use))] +#[cfg_attr(has_rust, cfg_attr(since(rust, "1.27"), must_use))] fn double(x: i32) -> i32 { 2 * x } @@ -177,10 +178,10 @@ fn main() { // ^--- This warning only happens if we are on Rust >= 1.27. } ``` -However, this would produce an `unexpected_cfgs` lint and you would need to add the following to `Cargo.toml`: +However, this would produce an `unexpected_cfgs` lint on the MSRV compiler and you can avoid that by adding the following to `Cargo.toml`: ```toml [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(has_rust,values(none()))'] } ``` Say you were wanting to test out `#[must_use]` after it got stabilized on nightly to provide feedback and to be ready for when it hits stable, @@ -274,6 +275,13 @@ Cargo will expose `rust` in: - build scripts as `CARGO_CFG_RUST` - `[target."cfg()".dependencies]` +## `has_rust` cfg + +A new built-in cfg `--cfg has_rust` will be added by the compiler +to allow checking of `rust` and `since` can be used. + +`has_rust` will be specified as `--check-cfg 'cfg(rust, values(none()))'` + ## clippy Clippy has a [`clippy::incompatible_msrv`](https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv) lint @@ -385,31 +393,28 @@ allow operators inside of the `cfg` `since` predicate, e.g. `#[cfg(since(rust, " However, with the rename of the predicate from `version` to `since`, operators don't fit in as easily. If someone wanted to support equality checks, there wouldn't be a way to support a continuous range of `values()` but would instead have to manually specify each likely potential version. -`--check-cfg` will cause the following to warn: +`--check-cfg` will cause the following to warn on older compilers: ```rust fn is_stderr_terminal() -> bool { - #[cfg(rust)] + #[cfg(has_rust)] #[cfg(since(rust, "1.70"))] use std::io::IsTerminal as _; - #[cfg(rust)] + #[cfg(has_rust)] #[cfg(not(since(rust, "1.70")))] use is_terminal::IsTerminal as _; - #[cfg(not(rust))] + #[cfg(not(has_rust))] use is_terminal::IsTerminal as _; std::io::stderr().is_terminal() } ``` -To allow checking for the presence of `rust`, add the following to your `Cargo.toml`: +To allow checking for the presence of `has_rust` without warnings on older compiler versions, +add the following to your `Cargo.toml`: ```toml [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(has_rust,values(none()))'] } ``` -Alternatively, we could have the built-in `--check-cfg` for `rust` include `values(none())` but: -- When building on an old version, users will see the warning and will likely want to add it anyways. -- We lose out on `--check-cfg` identifying misused. - Instead, we may wish to add a dedicated predicate intended for "is set". ## `rust` cfg rationale @@ -420,6 +425,20 @@ Rust does appear in some parts of the language, but is capitalized like with [`repr(Rust)`](https://doc.rust-lang.org/reference/type-layout.html?#the-rust-representation). However, the convention for `--cfg`s is generally lower case. +## `has_rust` cfg rationale + +We'd prefer to allow adoption of `since(rust, "")` +without requiring an MSRV bump so core packages with low MSRVs can take advantage of it immediately. +However, unknown predicates are a compiler error and there isn't a way to check if a cfg with a value is set, +so we added this cfg which is either set or unset and can be checked with `#[cfg(has_rust)]`. + +Without the context of `#[cfg(since(rust, ""))]`, +`has_rust` could be confusing (this compiling with a Rust compiler, why is it a question?). + +Other potential names: +- `rust_is_set` +- `has_since` + ### Pre-release When translating `rustc --version` to a language version, we have several choices when it comes to pre-releases, including: @@ -495,7 +514,7 @@ between the version it was first supported up to the current version, making the `=` operate as a "contains" operator, rather than an equality operator, like with `#[cfg(feature = "std")]`. -This was proposed to allow `#[cfg_attr(rust, cfg(rust = "1.95"))]` to more naturally allow adoption before the feature is stabilized. +This was proposed to allow `#[cfg_attr(rust = "1.95")]` to more naturally allow adoption before the feature is stabilized. This could be implemented statically, by hard coding the versions. This would work with `--print-cfg` and so would automatically work with Cargo. From e71bdc12412da0e4f09390e086013091f4d9c6b0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 14:46:51 -0500 Subject: [PATCH 041/109] fix(future): Include alternate name for 'before' --- text/3857-cfg-version.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index cbc691e1629..3636b0a4f58 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -640,6 +640,7 @@ Haskell: matching all version numbers. - Adding `#[cfg(before("1.95.0"))]` could resolve the unnatural grammar of `#[cfg(not(since("1.95.0")))]`. - Deferring to keep this minimal and to get more real world input on the usefulness of this + - Another possible name is `#[cfg(until("1.95.0"))]` which reads well as `#[cfg(not(until("1.95.0")))]` ## Relaxing SemVer From c12e4a4e8e67815cd787df572ec55f6a2bd50b2d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 14:49:49 -0500 Subject: [PATCH 042/109] Revert "fix: Introduce has_rust to replace incorrect use of cfg(rust)" This reverts commit 7139923e9ab594bd2aafc83b91cab7a5175b6a1c. --- text/3857-cfg-version.md | 51 +++++++++++++--------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 3636b0a4f58..19645f005cf 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -7,9 +7,8 @@ [summary]: #summary Allow Rust-version conditional compilation by adding -- `#[cfg(since(cfg_name, ""))]`, a minimum-version `cfg` predicate - a built-in `--cfg rust=`, for the Rust language version -- a built-in `--cfg has_rust`, for detecting the presence of the above +- `#[cfg(since(cfg_name, ""))]`, a minimum-version `cfg` predicate Say this was added before 1.70, you could do: ```toml @@ -164,10 +163,10 @@ fn main() { Now, let's say your MSRV is 1.10, the above code would error when compiling with your MSRV because the `since` predicate does not exist with that version. -However, the presence of `--cfg has_rust` implies that we are on 1.27, +However, the presence of `--cfg rust` implies that we are on 1.27, so you can "detect" support for `since` by changing your code to: ```rust -#[cfg_attr(has_rust, cfg_attr(since(rust, "1.27"), must_use))] +#[cfg_attr(rust, cfg_attr(since(rust, "1.27"), must_use))] fn double(x: i32) -> i32 { 2 * x } @@ -178,10 +177,10 @@ fn main() { // ^--- This warning only happens if we are on Rust >= 1.27. } ``` -However, this would produce an `unexpected_cfgs` lint on the MSRV compiler and you can avoid that by adding the following to `Cargo.toml`: +However, this would produce an `unexpected_cfgs` lint and you would need to add the following to `Cargo.toml`: ```toml [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(has_rust,values(none()))'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } ``` Say you were wanting to test out `#[must_use]` after it got stabilized on nightly to provide feedback and to be ready for when it hits stable, @@ -275,13 +274,6 @@ Cargo will expose `rust` in: - build scripts as `CARGO_CFG_RUST` - `[target."cfg()".dependencies]` -## `has_rust` cfg - -A new built-in cfg `--cfg has_rust` will be added by the compiler -to allow checking of `rust` and `since` can be used. - -`has_rust` will be specified as `--check-cfg 'cfg(rust, values(none()))'` - ## clippy Clippy has a [`clippy::incompatible_msrv`](https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv) lint @@ -393,28 +385,31 @@ allow operators inside of the `cfg` `since` predicate, e.g. `#[cfg(since(rust, " However, with the rename of the predicate from `version` to `since`, operators don't fit in as easily. If someone wanted to support equality checks, there wouldn't be a way to support a continuous range of `values()` but would instead have to manually specify each likely potential version. -`--check-cfg` will cause the following to warn on older compilers: +`--check-cfg` will cause the following to warn: ```rust fn is_stderr_terminal() -> bool { - #[cfg(has_rust)] + #[cfg(rust)] #[cfg(since(rust, "1.70"))] use std::io::IsTerminal as _; - #[cfg(has_rust)] + #[cfg(rust)] #[cfg(not(since(rust, "1.70")))] use is_terminal::IsTerminal as _; - #[cfg(not(has_rust))] + #[cfg(not(rust))] use is_terminal::IsTerminal as _; std::io::stderr().is_terminal() } ``` -To allow checking for the presence of `has_rust` without warnings on older compiler versions, -add the following to your `Cargo.toml`: +To allow checking for the presence of `rust`, add the following to your `Cargo.toml`: ```toml [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(has_rust,values(none()))'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } ``` +Alternatively, we could have the built-in `--check-cfg` for `rust` include `values(none())` but: +- When building on an old version, users will see the warning and will likely want to add it anyways. +- We lose out on `--check-cfg` identifying misused. + Instead, we may wish to add a dedicated predicate intended for "is set". ## `rust` cfg rationale @@ -425,20 +420,6 @@ Rust does appear in some parts of the language, but is capitalized like with [`repr(Rust)`](https://doc.rust-lang.org/reference/type-layout.html?#the-rust-representation). However, the convention for `--cfg`s is generally lower case. -## `has_rust` cfg rationale - -We'd prefer to allow adoption of `since(rust, "")` -without requiring an MSRV bump so core packages with low MSRVs can take advantage of it immediately. -However, unknown predicates are a compiler error and there isn't a way to check if a cfg with a value is set, -so we added this cfg which is either set or unset and can be checked with `#[cfg(has_rust)]`. - -Without the context of `#[cfg(since(rust, ""))]`, -`has_rust` could be confusing (this compiling with a Rust compiler, why is it a question?). - -Other potential names: -- `rust_is_set` -- `has_since` - ### Pre-release When translating `rustc --version` to a language version, we have several choices when it comes to pre-releases, including: @@ -514,7 +495,7 @@ between the version it was first supported up to the current version, making the `=` operate as a "contains" operator, rather than an equality operator, like with `#[cfg(feature = "std")]`. -This was proposed to allow `#[cfg_attr(rust = "1.95")]` to more naturally allow adoption before the feature is stabilized. +This was proposed to allow `#[cfg_attr(rust, cfg(rust = "1.95"))]` to more naturally allow adoption before the feature is stabilized. This could be implemented statically, by hard coding the versions. This would work with `--print-cfg` and so would automatically work with Cargo. From 28b52e14068577b24d838e81ca73588e413fc278 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 15:03:51 -0500 Subject: [PATCH 043/109] fix: Workaround #[cfg(rust)] with --cfg rust --- text/3857-cfg-version.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 19645f005cf..c2cda96a97e 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -255,7 +255,7 @@ So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, ## `rust` cfg -A new built-in cfg `--cfg rust=` will be added by the compiler +A new built-in cfg `--cfg=rust --cfg=rust=""` will be added by the compiler that specifies the language version. This will be the version of `rustc` with the behavior for pre-release versions being unspecified. We expect rustc to: @@ -408,7 +408,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } ``` Alternatively, we could have the built-in `--check-cfg` for `rust` include `values(none())` but: - When building on an old version, users will see the warning and will likely want to add it anyways. -- We lose out on `--check-cfg` identifying misused. +- We lose out on `--check-cfg` identifying misuses. Instead, we may wish to add a dedicated predicate intended for "is set". ## `rust` cfg rationale @@ -420,6 +420,8 @@ Rust does appear in some parts of the language, but is capitalized like with [`repr(Rust)`](https://doc.rust-lang.org/reference/type-layout.html?#the-rust-representation). However, the convention for `--cfg`s is generally lower case. +`--cfg=rust` is added to allow `#[cfg(rust)]` checks so packages can immediately adopt this feature without bumping an MSRV. + ### Pre-release When translating `rustc --version` to a language version, we have several choices when it comes to pre-releases, including: From b445f73f4dfabcf656973b3708a3a07e7d54d5ca Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 15:05:34 -0500 Subject: [PATCH 044/109] refactor(ref):? Include rust's --check-cfg in the rust-specific section --- text/3857-cfg-version.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index c2cda96a97e..639ae9918da 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -385,6 +385,17 @@ allow operators inside of the `cfg` `since` predicate, e.g. `#[cfg(since(rust, " However, with the rename of the predicate from `version` to `since`, operators don't fit in as easily. If someone wanted to support equality checks, there wouldn't be a way to support a continuous range of `values()` but would instead have to manually specify each likely potential version. +## `rust` cfg rationale + +While there was concern over `rust` appearing in the name of `cfg(rust_version("1.95"))`, +I feel that `rust` as its own entity makes sense and avoids that problem. + +Rust does appear in some parts of the language, +but is capitalized like with [`repr(Rust)`](https://doc.rust-lang.org/reference/type-layout.html?#the-rust-representation). +However, the convention for `--cfg`s is generally lower case. + +`--cfg=rust` is added to allow `#[cfg(rust)]` checks so packages can immediately adopt this feature without bumping an MSRV. + `--check-cfg` will cause the following to warn: ```rust fn is_stderr_terminal() -> bool { @@ -411,16 +422,6 @@ Alternatively, we could have the built-in `--check-cfg` for `rust` include `valu - We lose out on `--check-cfg` identifying misuses. Instead, we may wish to add a dedicated predicate intended for "is set". -## `rust` cfg rationale - -While there was concern over `rust` appearing in the name of `cfg(rust_version("1.95"))`, -I feel that `rust` as its own entity makes sense and avoids that problem. - -Rust does appear in some parts of the language, -but is capitalized like with [`repr(Rust)`](https://doc.rust-lang.org/reference/type-layout.html?#the-rust-representation). -However, the convention for `--cfg`s is generally lower case. - -`--cfg=rust` is added to allow `#[cfg(rust)]` checks so packages can immediately adopt this feature without bumping an MSRV. ### Pre-release From 5eef426882fc3e6ac640b084b60f15b21cec874f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 15:53:14 -0500 Subject: [PATCH 045/109] fix(ref): Be more precise when talking about check-cfg --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 639ae9918da..68bf5efdc18 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -233,7 +233,7 @@ CheckConfigurationSince -> `since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) ` The syntax for the contents of the string literal is a SemVer value without the build field. -This will specify that for the given cfg, values will be valid if: +This will specify that for the given cfg, literals will be valid if: - SemVer syntax - from the specified version and up From 9da21e1fc8f60c16095916004fcd4353b2c12cd9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 16:09:04 -0500 Subject: [PATCH 046/109] fix(ref): Correct where we cross-link check-cfg for since --- text/3857-cfg-version.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 68bf5efdc18..72c914159f2 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -215,11 +215,11 @@ If `IDENTIFIER` has no value or is undefined, this will evaluate to `false`. If `IDENTIFIER` is not a valid [SemVer](https://semver.org/) value, this will evaluate to `false`. -*(for warning on always-false checks, see `--check-cfg`)* - If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, this will evaluate to `false`. Note that this excludes support for the `+build` field. +*(for warning on always-false checks, see `--check-cfg`)* + Otherwise, the `IDENTIFIER` will be compared to the string literal according to [Cargo's `>=` version requirements](https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#comparison-requirements). For example, `#[cfg(since(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. From d27c10d6f5b39e019c343155f70ca2ddd1473dff Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 16:13:17 -0500 Subject: [PATCH 047/109] fix(ref): Separate since logic for unset/name-only --- text/3857-cfg-version.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 72c914159f2..1de8fb74583 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -211,7 +211,9 @@ The [syntax](https://doc.rust-lang.org/reference/conditional-compilation.html#gr ConfigurationSince -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` ``` -If `IDENTIFIER` has no value or is undefined, this will evaluate to `false`. +If `IDENTIFIER` is unset, this will evaluate to `false`. + +If `IDENTIFIER` is name-only, this will evaluate to `false`. If `IDENTIFIER` is not a valid [SemVer](https://semver.org/) value, this will evaluate to `false`. From 0a839bd81f5cadbc32e2e5400536057a0772311f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 16:25:23 -0500 Subject: [PATCH 048/109] fix(rationale): Acknowledge impact of cfg(rust) on cfg_value --- text/3857-cfg-version.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 1de8fb74583..7c8c0fe5bfc 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -397,6 +397,7 @@ but is capitalized like with [`repr(Rust)`](https://doc.rust-lang.org/reference/ However, the convention for `--cfg`s is generally lower case. `--cfg=rust` is added to allow `#[cfg(rust)]` checks so packages can immediately adopt this feature without bumping an MSRV. +This does lock us into how a `cfg_value!(rust)` would work from the [future-possibilities]. `--check-cfg` will cause the following to warn: ```rust @@ -673,9 +674,26 @@ See also [`#[cfg(nightly)]`](https://rust-lang.github.io/rfcs/2523-cfg-path-vers ## Provide a way to get a `--cfg`s value -Similar to how `cfg!` allows doing conditionals in Rust code, provide a "`cfg_value!`" for reading the value. -On top of [other use cases](https://internals.rust-lang.org/t/pre-rfc-mutually-excusive-global-features/19618) for `cfg_value!`, -this would allow an application to approximate the vendor version `--bugreport` / `-v --version` without a build script. +Use cases: +- Allows application to use `rust` to approximate the vendor version in `--bugreport` / `-v --version` without a build script. + As other versions get represented in `cfg`, this may be desired for the same reason. +- See also [mutually exclusive features](https://internals.rust-lang.org/t/pre-rfc-mutually-excusive-global-features/19618) for `cfg_value!`, + +Similar to how `cfg!` allows doing conditionals in Rust code, +provide macros for reading the values set for a `cfg`. + +The most general form maybe `cfg_values!(foo)` but a `cfg_value!(foo)` could offer some convenience. + +Open questions: +- How does `cfg_values!(foo)` deal with unset and name-only cfg's? + - Most strict would be `Iterator>`, requiring users to do `cfg_values!(foo).filter_map(std::convert::identity)` in most cases + - Could auto-skip name-only. Empty iterator would then be ambigious. +- How does `cfg_value!(foo)` deal being unset? + - Compiler error, like `env!`. Could provide an `option_cfg_value!`. +- How does `cfg_value!(foo)` deal with name-only cfgs?? + - Ignoring them would work best for the purpose of `--cfg=rust --cfg=rust="1.95.0"` +- How does `cfg_value!(foo)` deal with multiple cfg vales? + - Compiler error ## `check-cfg` support for a version without a minimum From 005c60888c2404f05ba2cb3d8121dc247104629c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 16:41:28 -0500 Subject: [PATCH 049/109] fix(ref): Specify when multiple cfg's are present for the same name --- text/3857-cfg-version.md | 44 +++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 7c8c0fe5bfc..11a06e26cd1 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -211,20 +211,30 @@ The [syntax](https://doc.rust-lang.org/reference/conditional-compilation.html#gr ConfigurationSince -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` ``` -If `IDENTIFIER` is unset, this will evaluate to `false`. - -If `IDENTIFIER` is name-only, this will evaluate to `false`. - -If `IDENTIFIER` is not a valid [SemVer](https://semver.org/) value, this will evaluate to `false`. - -If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, this will evaluate to `false`. -Note that this excludes support for the `+build` field. +When evaluating `since`, +1. If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, this will evaluate to `false`.
+ *(for warning on always-false checks, see `--check-cfg`)* +2. If `IDENTIFIER` is unset, this will evaluate to `false`. +3. If any of the following evaluates to `true` for any cfg entry for `IDENTIFIER`, `since` will evaluate to `true`, otherwise `false`. + 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. + 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, this entry will evaluate to `false`. + Note that this excludes support for the `+build` field. + 3. Otherwise, the `IDENTIFIER`s value will be compared to the string literal according to +[Cargo's `>=` version requirements](https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#comparison-requirements). + For example, `#[cfg(since(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. -*(for warning on always-false checks, see `--check-cfg`)* +Examples: +- `cfg(since(unset_name, "1.0.0"))` will be false +- `--cfg name_only` and `cfg(since(name_only, "1.0.0"))` will be false +- `--cfg foo="bird"` and `cfg(since(name_only, "1.0.0"))` will be false +- `--cfg foo="1.1.0"` and `cfg(since(foo, "bird"))` will be false +- `--cfg foo="1.1.0"` and `cfg(since(foo, "1.2.0"))` will be true +- `--cfg foo="1.1.0"` and `cfg(since(foo, "1.0.0"))` will be false +- `--cfg foo --cfg foo="1.1.0" --cfg foo="1.0.0"` and `cfg(since(foo, "1.0.0"))` will be true -Otherwise, the `IDENTIFIER` will be compared to the string literal according to -[Cargo's `>=` version requirements](https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#comparison-requirements). -For example, `#[cfg(since(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. +The compiler implementation currently treats cfgs as `HashSet<(String, Option)` +and would likely need to change this to `HashMap>>`` +to accommodate this predicate. ## `--check-cfg` @@ -362,6 +372,16 @@ This would also be inconsistent with other uses of `cfg`s *but* maybe that would just be the start to natively supporting more types in `cfg`, like integers which are of interest to embedded folks. +A user could do `--cfg=foo --cfg=foo="1.2.0" --cfg=foo"1.3.0"`, leading to `cfg` to be a set of: +- `("foo", None)` +- `("foo", "1.2.0")` +- `("foo", "1.3.0")` + +meaning `cfg(all(foo, foo = "1.2.0", foo = "1.3.0"))` is `true`. + +We take this into account by checking if any cfg with the name `foo` matches `since`. +Alternatively, we could fail the match in this case but that prevents `--cfg rust` for checking if this feature is stable. + ## `--check-cfg` rationale The `--check-cfg` predicate and the value for `rust` ensures users get warnings about From 5d017217d6941858e03576fb0bc27f3cedbbee3a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 17:11:49 -0500 Subject: [PATCH 050/109] style: Fix spelling --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 11a06e26cd1..931ff4872c1 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -707,7 +707,7 @@ The most general form maybe `cfg_values!(foo)` but a `cfg_value!(foo)` could off Open questions: - How does `cfg_values!(foo)` deal with unset and name-only cfg's? - Most strict would be `Iterator>`, requiring users to do `cfg_values!(foo).filter_map(std::convert::identity)` in most cases - - Could auto-skip name-only. Empty iterator would then be ambigious. + - Could auto-skip name-only. Empty iterator would then be ambiguous. - How does `cfg_value!(foo)` deal being unset? - Compiler error, like `env!`. Could provide an `option_cfg_value!`. - How does `cfg_value!(foo)` deal with name-only cfgs?? From e036b233951de00162b91ab1488bea50279e276c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 17:10:35 -0500 Subject: [PATCH 051/109] style: Be more consistent in quoting --- text/3857-cfg-version.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 931ff4872c1..8ac0789d01a 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -249,7 +249,7 @@ This will specify that for the given cfg, literals will be valid if: - SemVer syntax - from the specified version and up -Specifically when the given cfg is used with the `cfg` `since` predicate: +Specifically when the given cfg is used with the cfg `since` predicate: - the string literal should not be of a syntax that evaluates to `false` - the string literal must be a minimum version requirement that specifies a subset of what the `--check-cfg` specifies @@ -392,7 +392,7 @@ The `--check-cfg` predicate and the value for `rust` ensures users get warnings in case we want the future possibility of relaxing SemVer versions *and* we want to infer from the fields used in `--check-cfg` to specify the maximum number of fields accepted in comparisons. -We could have the `check-cfg` `since` predicate only apply to the `cfg` `since` predicate, +We could have the check-cfg `since` predicate only apply to the cfg `since` predicate, causing `#[cfg(rust = "1.100.0")]` to warn. However, - the `since` predicates are a general feature intended to be used with other version numbers where exact matches may also be appropriate. @@ -400,10 +400,10 @@ However, Possibly there could be a clippy lint specifically about `rust = ""`. Alternatively, we could try to find a way to structure `--check-cfg` to allow the person setting the `check-cfg` to decide whether it can be used with `=` or not. -One way of doing this is by allowing the `check-cfg` `since` predicate outside of the `values` predicate, -meaning it works with the `cfg` `since` predicate and not the `=` operator. -Another way would be for the `check-cfg` `since` predicate to never work with `=` but to instead -allow operators inside of the `cfg` `since` predicate, e.g. `#[cfg(since(rust, "=1.95.0"))]`. +One way of doing this is by allowing the check-cfg `since` predicate outside of the `values` predicate, +meaning it works with the cfg `since` predicate and not the `=` operator. +Another way would be for the check-cfg `since` predicate to never work with `=` but to instead +allow operators inside of the cfg `since` predicate, e.g. `#[cfg(since(rust, "=1.95.0"))]`. However, with the rename of the predicate from `version` to `since`, operators don't fit in as easily. If someone wanted to support equality checks, there wouldn't be a way to support a continuous range of `values()` but would instead have to manually specify each likely potential version. @@ -651,7 +651,7 @@ Haskell: ## Relaxing SemVer -Instead of requiring the `IDENTIFIER` in the `check-cfg` `since` predicate to be strictly SemVer `major.minor.patch`, +Instead of requiring the `IDENTIFIER` in the check-cfg `since` predicate to be strictly SemVer `major.minor.patch`, we could allow abbreviated forms like `major.minor` or even `major`. This would make the predicate more inclusive for other cases, like `edition`. From 940b979146e9d1939e19b25f8d5da941a8ff0a6e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 16:52:29 -0500 Subject: [PATCH 052/109] fix(ref): Turn non-semver 'since' from false to error --- text/3857-cfg-version.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 8ac0789d01a..845bf0614b5 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -212,12 +212,12 @@ ConfigurationSince -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_L ``` When evaluating `since`, -1. If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, this will evaluate to `false`.
+1. If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, the compiler will error *(for warning on always-false checks, see `--check-cfg`)* 2. If `IDENTIFIER` is unset, this will evaluate to `false`. 3. If any of the following evaluates to `true` for any cfg entry for `IDENTIFIER`, `since` will evaluate to `true`, otherwise `false`. 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. - 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, this entry will evaluate to `false`. + 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, the compiler will error Note that this excludes support for the `+build` field. 3. Otherwise, the `IDENTIFIER`s value will be compared to the string literal according to [Cargo's `>=` version requirements](https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#comparison-requirements). @@ -358,9 +358,12 @@ While having a specific name avoids these concerns. We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(since("1.95"))]` as a shorthand. However, this would look confusing in Cargo and doesn't seem like its offering enough of a benefit to be worth the costs. -The `ConfigurationSince` is sloppy with the string literal's syntax (relying on `--check-cfg`) so that -- Allows evolution without requiring an MSRV bump -- Its consistent with other predicates, e.g. `#[cfg(foo = "1.0")]` +`ConfigurationSince` requires the `IDENTIFIER` and string literal to be a SemVer version +so we can have the flexibility to relax the syntax over time without it being a breaking change +For example, if `--cfg=foo="1.0"` caused `cfg(since(foo, "1.0"))` to be `false` and we later allowed `"1.0"` for the `IDENTIFIER`, it would now be `true` and would change behavior. +Because we'll have `since(rust, _)` at that point, it won't require an MSRV bump. +This does leave the door open for us to relax this in the future once we become comfortable with the flexibility of our version syntax. +Alternatively, we could try to determine a flexible-enough version syntax now though that comes with the risk that it isn't sufficient. If we were stricter on the syntax, we could allow for version numbers to be directly accepted, without quotes From b37fe10c7a0e35757fbed42d8dce1d16f160df4f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 14:03:06 -0500 Subject: [PATCH 053/109] feat(future): Speak to how `edition` would work --- text/3857-cfg-version.md | 82 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 845bf0614b5..ccdcb32e5ba 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -654,18 +654,92 @@ Haskell: ## Relaxing SemVer -Instead of requiring the `IDENTIFIER` in the check-cfg `since` predicate to be strictly SemVer `major.minor.patch`, +Instead of requiring the `IDENTIFIER` in the cfg `since` predicate to be strictly SemVer `major.minor.patch`, we could allow abbreviated forms like `major.minor` or even `major`. This would make the predicate more inclusive for other cases, like `edition`. +The syntax for a version could be: +``` +Version -> + ReleaseVersion + PrereleaseVersion? + +ReleaseVersion -> + VersionField + ( `.` VersionField)* + +PrereleaseVersion -> + `-` + VersionField + ( `.` VersionField)* + +VersionField -> ( NumericVersionField | AlphanumericVersionField ) + +NumericVersionField -> + `0` + | ( [`1`..`9`] DEC_DIGIT* ) + +AlphanumericVersionField -> ( + DEC_DIGIT + | [`a`..`z`] + | [`A`..`Z`] + | `-` + )+ +``` + +With the precedence of: +- Precedence is calculated by separating the `Version` into the respective `VersionField`s +- Precedence is determined by the first difference when comparing each field from left to right of `ReleaseVersion` + - `NumericVersionField` is compared numerically + - `AlphanumericVersionField` is compared lexically in ASCII sort order + - Numeric identifiers always have lower precedence than non-numeric identifiers + - When two versions have different number of fields, the missing fields are assumed to be `0` +- When the two `ReleaseVersion`s are equal, a `Version` with a `PrereleaseVersion` has lower precedence than one without +- Precedence for two `Version`s with the matching `ReleaseVersion`s but different `PrereleaseVersion`s is determined by the first difference when comparing each field from left to right of `PrereleaseVersion` + - `NumericVersionField` is compared numerically + - `AlphanumericVersionField` is compared lexically in ASCII sort order + - Numeric identifiers always have lower precedence than non-numeric identifiers + - `PrereleaseVersion` with more `VersionField`s has a higher precedence than one with less, if all of the preceding `VersionField`s are equal. + +This was adopted from [SemVer](https://semver.org/) with the following changes: +- Arbitrary precision for `ReleaseVersion` + - Unlike `PrereleaseVersion`, missing fields is assumed to be `0`, rather than lower precedence +- Alphanumerics are allowed in release version fields + +The string literal for cfg `since` and check-cfg `since` would be similarly updated. +A user would see the `unexpected_cfgs` lint if their cfg `since` string literal had more precision (more `VersionField`s) than the check-cfg `since` predicate. + +Note: for `--cfg foo="bar"`, `"bar"` would be a valid version. + +## `--cfg edition` + +In adding a `cfg` for the Edition, we could model it as either: +- An integer +- A single-field version + +Assuming the latter, +we could have the following definition, building on the above relaxing of SemVer: + +`--cfg edition=""` + +`--check-cfg cfg(edition, values(2015, 2018, 2021, 2024, since(2025)))` +- The discrete values for known editions is there to help catch mistakes +- `since(2025)` is used so packages don't have to deal with `unexpected_cfgs` when operating with edition versions higher than their current compiler recognizes and without having to try to predict what our future edition versions and policies may be +- `since(2025)` also ensures that a user gets an `unexpected_cfgs` warning if they do `cfg(since(edition, 2028.10))` as that matches the `since(2025)` but has more precision + ## `cfg_target_version` Instead of defining a new `#[cfg]` predicate, [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750) could reuse the `#[cfg(since)]` predicate. -As not all systems use SemVer, we can either -- Contort the version into SemVer - - This can run into problems either with having more precision (e.g. `120.0.1.10` while SemVer only allows `X.Y.Z`) or post-release versions (e.g. [`1.2.0.post1`](https://packaging.python.org/en/latest/discussions/versioning/) which, if we translated it to SemVer's syntax of `1.2.0-post1`, would be treated as a pre-release). +Building on the above relaxing of Semver, we should meat the needs of most versioning systems. +The one known exception is "post releases" +(e.g. [`1.2.0.post1`](https://packaging.python.org/en/latest/discussions/versioning/) +which, if we translated it to SemVer's syntax of `1.2.0-post1`, would be treated as a pre-release. +We can translate this to extra precision, e.g. `1.2.0-post1` could be `1.2.0.post1`. +This would require the check-cfg `since` to use the appropriate amount of precision to not warn. + +If this is still not sufficient, we some options include: - Add an optional third field for specifying the version format (e.g. `#[cfg(since(windows, "10.0.10240", )]`) - Make `--check-cfg` load-bearing by having the version policy name be specified in the `--check-cfg` predicate From 2c3cbe4e0cb1fbe9783d8a5522ae5d318d5f7989 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 17:22:17 -0500 Subject: [PATCH 054/109] fix(drawbacks): Speak to general vs specialized solutions --- text/3857-cfg-version.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index ccdcb32e5ba..3e89601678d 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -320,6 +320,10 @@ this does not cover the use case from [rustversion](https://crates.io/crates/rus Libraries could having ticking time bombs that accidentally break or have undesired behavior for some future Rust version that can't be found until we hit that version. +Compared to the alternative designs, +this may take more time in design discussions, implementation, and vetting the implementation +as there are more corner cases to cover, particularly with how this integrates with future possibilities. + ## Pre-releases for major versions Pre-releases of major versions isn't a consideration for `rust` but in the general use of `since`. @@ -515,6 +519,9 @@ Similarly, if we stabilized `#[stable(since)]`, a linter could report when a ver We could rename this to `version` and stabilize it as-is, with this RFC being a future possibility that adds an optional second parameter for specifying which version is being referred to. +This ends up being a one-off solution, +requiring other one-off solutions for `edition`, [`target_version`](https://github.com/rust-lang/rfcs/pull/3750), etc. + ### `cfg(rust = "1.95")` *(this [idea](https://github.com/rust-lang/rust/pull/141766#issuecomment-2940720778) came up on the stabilization PR for RFC 2523)* From 75c486b469d6ae5908b203c321feaa749b8bd8fb Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 20:15:02 -0500 Subject: [PATCH 055/109] fix(rationale): Include another benefit for non-semver erroring --- text/3857-cfg-version.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 3e89601678d..05b3e30597e 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -362,12 +362,14 @@ While having a specific name avoids these concerns. We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(since("1.95"))]` as a shorthand. However, this would look confusing in Cargo and doesn't seem like its offering enough of a benefit to be worth the costs. -`ConfigurationSince` requires the `IDENTIFIER` and string literal to be a SemVer version +`ConfigurationSince` requires the `IDENTIFIER` and string literal to be a SemVer version, +erroring otherwise, so we can have the flexibility to relax the syntax over time without it being a breaking change For example, if `--cfg=foo="1.0"` caused `cfg(since(foo, "1.0"))` to be `false` and we later allowed `"1.0"` for the `IDENTIFIER`, it would now be `true` and would change behavior. Because we'll have `since(rust, _)` at that point, it won't require an MSRV bump. This does leave the door open for us to relax this in the future once we become comfortable with the flexibility of our version syntax. Alternatively, we could try to determine a flexible-enough version syntax now though that comes with the risk that it isn't sufficient. +Another benefit to erroring is so `not(since(invalid, ""))` is not `true`. If we were stricter on the syntax, we could allow for version numbers to be directly accepted, without quotes From 602eec6788150ba78436c5489de558f16ac1a41e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 20:15:53 -0500 Subject: [PATCH 056/109] fix(ref): Syntax error Co-authored-by: Urgau <3616612+Urgau@users.noreply.github.com> --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 05b3e30597e..9412545bfd4 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -232,7 +232,7 @@ Examples: - `--cfg foo="1.1.0"` and `cfg(since(foo, "1.0.0"))` will be false - `--cfg foo --cfg foo="1.1.0" --cfg foo="1.0.0"` and `cfg(since(foo, "1.0.0"))` will be true -The compiler implementation currently treats cfgs as `HashSet<(String, Option)` +The compiler implementation currently treats cfgs as `HashSet<(String, Option)>` and would likely need to change this to `HashMap>>`` to accommodate this predicate. From e352338cccce96fdffe4e83735ef34dd3fa2c276 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Sep 2025 20:20:23 -0500 Subject: [PATCH 057/109] fix(rationale): Cover a little more about extending version syntax --- text/3857-cfg-version.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 9412545bfd4..a5a7484f83c 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -371,6 +371,12 @@ This does leave the door open for us to relax this in the future once we become Alternatively, we could try to determine a flexible-enough version syntax now though that comes with the risk that it isn't sufficient. Another benefit to erroring is so `not(since(invalid, ""))` is not `true`. +Deferring the more flexible syntax avoids having to couple this decision to what syntax should be allowed +which will allow us to better evaluate the ramifications for each time we relax things. +For instance, in the [future-possibilities] we go so far as to allow alphabetic characters in any field while making the precision arbitrary. +This can have side effects like allowing comparing words like with `#[cfg(since(hello, "world"))]`, +whether intended by the users (potential abuse of the feature) or not (masking errors that could help find bugs). + If we were stricter on the syntax, we could allow for version numbers to be directly accepted, without quotes (e.g. `#[cfg(since(rust, 1.95.0))]`). From 995eeed10c610e4fe3035154321ef7c0fd599157 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Sep 2025 09:51:26 -0500 Subject: [PATCH 058/109] fix(future): Discuss a slow roll out of relaxing version definition --- text/3857-cfg-version.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index a5a7484f83c..e89d92fb40b 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -678,6 +678,7 @@ The syntax for a version could be: Version -> ReleaseVersion PrereleaseVersion? + BuildMetadata? ReleaseVersion -> VersionField @@ -688,6 +689,11 @@ PrereleaseVersion -> VersionField ( `.` VersionField)* +BuildMetadata -> + `+` + VersionField + ( `.` VersionField)* + VersionField -> ( NumericVersionField | AlphanumericVersionField ) NumericVersionField -> @@ -703,7 +709,7 @@ AlphanumericVersionField -> ( ``` With the precedence of: -- Precedence is calculated by separating the `Version` into the respective `VersionField`s +- Precedence is calculated by separating the `Version` into the respective `VersionField`s, ignoring `BuildMetadata` - Precedence is determined by the first difference when comparing each field from left to right of `ReleaseVersion` - `NumericVersionField` is compared numerically - `AlphanumericVersionField` is compared lexically in ASCII sort order @@ -721,11 +727,17 @@ This was adopted from [SemVer](https://semver.org/) with the following changes: - Unlike `PrereleaseVersion`, missing fields is assumed to be `0`, rather than lower precedence - Alphanumerics are allowed in release version fields -The string literal for cfg `since` and check-cfg `since` would be similarly updated. +The version requirement (string literal) for cfg `since` and check-cfg `since` would be similarly updated +except `BuildMetadata` would not be allowed. A user would see the `unexpected_cfgs` lint if their cfg `since` string literal had more precision (more `VersionField`s) than the check-cfg `since` predicate. Note: for `--cfg foo="bar"`, `"bar"` would be a valid version. +We could always relax this incrementally, e.g. +- Variable precision for `edition` +- `BuildMetadata` for dependency versions +- Whatever `target_version` requires + ## `--cfg edition` In adding a `cfg` for the Edition, we could model it as either: @@ -733,7 +745,7 @@ In adding a `cfg` for the Edition, we could model it as either: - A single-field version Assuming the latter, -we could have the following definition, building on the above relaxing of SemVer: +we could have the following definition, building on the above relaxing of SemVer for at least variable alternative precision: `--cfg edition=""` @@ -764,6 +776,11 @@ As the ecosystem grows and matures, the Rust language and standard library may not be the only dependencies users wish to support multiple versions of. We may want to allow `#(cfg(since(serde, "1.0.900")]`. +As dependency versions can have build numbers, +we'd need to decide whether to further relax version numbers by allowing build numbers +which would not affect precedence or whether the caller is responsible for stripping them, +losing potential release information. + ## Vendor name and version We could add `--cfg`s for the compiler vendor name and version. From c7ebc4066f59f1d52cfb4e8f409c3a117d13c6a4 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Sep 2025 09:54:05 -0500 Subject: [PATCH 059/109] fix(alt): Cover has_rust --- text/3857-cfg-version.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index e89d92fb40b..a2a2bb8e43f 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -433,6 +433,7 @@ However, the convention for `--cfg`s is generally lower case. `--cfg=rust` is added to allow `#[cfg(rust)]` checks so packages can immediately adopt this feature without bumping an MSRV. This does lock us into how a `cfg_value!(rust)` would work from the [future-possibilities]. +Alternatively, we could add a separate cfg, like `has_rust`, `rust_is_set`, `has_since`. `--check-cfg` will cause the following to warn: ```rust From 679116ad58f1b3c9c0b5ec4f821109c85f5d39f2 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Sep 2025 09:55:39 -0500 Subject: [PATCH 060/109] fix(alt): Include cfg rust_version name --- text/3857-cfg-version.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index a2a2bb8e43f..8e86f4fa876 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -431,6 +431,10 @@ Rust does appear in some parts of the language, but is capitalized like with [`repr(Rust)`](https://doc.rust-lang.org/reference/type-layout.html?#the-rust-representation). However, the convention for `--cfg`s is generally lower case. +Alternatively, we could call this `rust_version`. +The lack of a qualifier happens to work in this case but that might not be universally true +and adding the qualifier now may improve consistency with the future. + `--cfg=rust` is added to allow `#[cfg(rust)]` checks so packages can immediately adopt this feature without bumping an MSRV. This does lock us into how a `cfg_value!(rust)` would work from the [future-possibilities]. Alternatively, we could add a separate cfg, like `has_rust`, `rust_is_set`, `has_since`. From 5accadad3343042ae459812d97558a1e0cff86c7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Sep 2025 09:58:52 -0500 Subject: [PATCH 061/109] feat(unresolved): Call out main T-lang questions within RFC --- text/3857-cfg-version.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 8e86f4fa876..3f9f5836d25 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -663,6 +663,10 @@ Haskell: # Unresolved questions [unresolved-questions]: #unresolved-questions +- `rust` or `rust_version`? +- `--cfg rust` or `--cfg has_rust` for using now without an MSRV bump? +- How strict should the version syntax be at this stage? + # Future possibilities [future-possibilities]: #future-possibilities From 19dca3e12e118d8f8258f3b68371ac342c2088c7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Sep 2025 10:24:28 -0500 Subject: [PATCH 062/109] feat(alt): Add an operator variant --- text/3857-cfg-version.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 3f9f5836d25..63072a34657 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -354,6 +354,8 @@ The `since` name was taken from [rustversion](https://crates.io/crates/rustversion) and the `#[deprecated(since)]` / `#[stable(since)]` attributes. This better conveys what operation is being performed than the original `version` name and leaves room for related predicates like `before`. +In particular, as this is a general feature and not just for Rust version comparisons, +we need to consider cases like `version(python, "2.8")` and whether people would interpret that as an exact match, a SemVer match, or a `>=` match (the winner). We could also call this `minimum`, or support comparison operators in the spirit of [RFC 3796](https://github.com/rust-lang/rfcs/pull/3796). The risk with a general word like `since` is if we gain support for other data types in cfgs, like integers for embedded development. The name `since` might apply in some situations but not others and its unclear if we'd want to generalize it past versions. @@ -509,6 +511,30 @@ so we do not include that information. ## Alternative designs +### `version(rust, ">=1.95")` + +Instead of having an assumed operator for the predicate, +we could require an operator as either: +- `version(rust, ">=1.95")` +- `version(rust >= "1.95")` + +For Cargo, operators do not match pre-release versions unless the operand uses them +though this may be relaxed, see [cargo#14305](https://github.com/rust-lang/cargo/pull/14305). +This does not fit with out use cases because it causes discontinuities +while users of the `cfg` need continuity. + +This allows moving to a more specialized predicate name than `since` without losing the conveyed meaning. + +If the operator is outside of the string literal +- we could also make it a bare word but that could lead to problems when dealing with relaxing of the version syntax +- this creates a DSL inside our existing DSL which feels tacked on like using [rustversion](https://crates.io/crates/rustversion) +- We'd need to decide how far to extend this DSL + +If the operator is inside the string literal +- this would feel comfortably familiar due to Cargo +- users may stumble and be frustrated with missing features from cargo (do we include all unary and binary operators?) +- behavior differences with Cargo may be needed due to different use cases but could lead to user bugs and frustration as it might not match what users are familiar with + ### `cfg(rust_version(1.95))` *(this is [RFC 2523](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html))* @@ -666,6 +692,7 @@ Haskell: - `rust` or `rust_version`? - `--cfg rust` or `--cfg has_rust` for using now without an MSRV bump? - How strict should the version syntax be at this stage? +- `since(rust, "1.95")`, `version(rust, ">=1.95")`, or `version(rust >= "1.95")` # Future possibilities [future-possibilities]: #future-possibilities From 12f674529dff937b111cd73ffb05420f293bf874 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Sep 2025 10:36:16 -0500 Subject: [PATCH 063/109] feat(unresolved): Include discussion of nightlies --- text/3857-cfg-version.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 63072a34657..0df1f12b0f2 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -693,6 +693,7 @@ Haskell: - `--cfg rust` or `--cfg has_rust` for using now without an MSRV bump? - How strict should the version syntax be at this stage? - `since(rust, "1.95")`, `version(rust, ">=1.95")`, or `version(rust >= "1.95")` +- Is `"1.95.0-incomplete"` an acceptable compromise on the question of whether to treat nightlies as complete or incomplete? # Future possibilities [future-possibilities]: #future-possibilities From 303bb644ec4f2c6935d9166fcb3730c75a2df9ca Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Sep 2025 10:37:48 -0500 Subject: [PATCH 064/109] style: Try to fix formatting --- text/3857-cfg-version.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 0df1f12b0f2..4fda3ffae09 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -216,12 +216,12 @@ When evaluating `since`, *(for warning on always-false checks, see `--check-cfg`)* 2. If `IDENTIFIER` is unset, this will evaluate to `false`. 3. If any of the following evaluates to `true` for any cfg entry for `IDENTIFIER`, `since` will evaluate to `true`, otherwise `false`. - 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. - 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, the compiler will error - Note that this excludes support for the `+build` field. - 3. Otherwise, the `IDENTIFIER`s value will be compared to the string literal according to + 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. + 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, the compiler will error + Note that this excludes support for the `+build` field. + 3. Otherwise, the `IDENTIFIER`s value will be compared to the string literal according to [Cargo's `>=` version requirements](https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#comparison-requirements). - For example, `#[cfg(since(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. + For example, `#[cfg(since(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. Examples: - `cfg(since(unset_name, "1.0.0"))` will be false From 4347c60bc5dbe0549528c637e092b905826f84f3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 18 Sep 2025 10:38:57 -0500 Subject: [PATCH 065/109] fix(ref): Update examples for changes to compiler errors --- text/3857-cfg-version.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 4fda3ffae09..8186d9aaafc 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -226,8 +226,8 @@ When evaluating `since`, Examples: - `cfg(since(unset_name, "1.0.0"))` will be false - `--cfg name_only` and `cfg(since(name_only, "1.0.0"))` will be false -- `--cfg foo="bird"` and `cfg(since(name_only, "1.0.0"))` will be false -- `--cfg foo="1.1.0"` and `cfg(since(foo, "bird"))` will be false +- `--cfg foo="bird"` and `cfg(since(name_only, "1.0.0"))` will be a compiler error +- `--cfg foo="1.1.0"` and `cfg(since(foo, "bird"))` will be a compiler error - `--cfg foo="1.1.0"` and `cfg(since(foo, "1.2.0"))` will be true - `--cfg foo="1.1.0"` and `cfg(since(foo, "1.0.0"))` will be false - `--cfg foo --cfg foo="1.1.0" --cfg foo="1.0.0"` and `cfg(since(foo, "1.0.0"))` will be true From 53e0d3996b439ecfe176c871f2996760ce264cf7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 11:56:27 -0500 Subject: [PATCH 066/109] fix(ref): Address a cardinality issue --- text/3857-cfg-version.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 8186d9aaafc..5e6fbb825ab 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -228,9 +228,9 @@ Examples: - `--cfg name_only` and `cfg(since(name_only, "1.0.0"))` will be false - `--cfg foo="bird"` and `cfg(since(name_only, "1.0.0"))` will be a compiler error - `--cfg foo="1.1.0"` and `cfg(since(foo, "bird"))` will be a compiler error -- `--cfg foo="1.1.0"` and `cfg(since(foo, "1.2.0"))` will be true -- `--cfg foo="1.1.0"` and `cfg(since(foo, "1.0.0"))` will be false -- `--cfg foo --cfg foo="1.1.0" --cfg foo="1.0.0"` and `cfg(since(foo, "1.0.0"))` will be true +- `--cfg foo="1.1.0"` and `cfg(since(foo, "1.0.0"))` will be true +- `--cfg foo="1.1.0"` and `cfg(since(foo, "1.2.0"))` will be false +- `--cfg foo --cfg foo="1.1.0" --cfg foo="1.0.0"` and `cfg(since(foo, "1.1.0"))` will be true The compiler implementation currently treats cfgs as `HashSet<(String, Option)>` and would likely need to change this to `HashMap>>`` From 8a57b6b8ae437a49f4e30ad5fd2757093a4e7175 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:31:48 -0500 Subject: [PATCH 067/109] fix(summary): Fully specify the cfg --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 5e6fbb825ab..ec839390197 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -7,7 +7,7 @@ [summary]: #summary Allow Rust-version conditional compilation by adding -- a built-in `--cfg rust=`, for the Rust language version +- a built-in `--cfg=rust --cfg=rust=""`, for the Rust language version - `#[cfg(since(cfg_name, ""))]`, a minimum-version `cfg` predicate Say this was added before 1.70, you could do: From 97d86b63b1bee0ceae73006e3361adf8a62fdb66 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:33:12 -0500 Subject: [PATCH 068/109] fix(motivation): Re-order for clarity --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index ec839390197..17dbb199ebe 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -33,9 +33,9 @@ This supersedes the `cfg_version` subset of [RFC 2523](https://rust-lang.github. [motivation]: #motivation These problems mostly have solutions today through third-party crates or other patterns but -- The workarounds for Rust-version-specific dependencies are less straightforward and difficult to get right. - That requires knowledge of them when users expect this to "just work" like in other ecosystems - They slow down build times, requiring at least one build script to be fully built (even in `cargo check`) and then executed. In one sample "simple webserver", in the dependency tree there were 10 build scripts and 2 proc-macros built for the sake of version detection. +- The workarounds for Rust-version-specific dependencies are less straightforward and difficult to get right. ## Use cases From 6241a5df038d9bb990cbb8aaf63b6add6f055c0f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:34:44 -0500 Subject: [PATCH 069/109] style: Fix grammar --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 17dbb199ebe..6778b90cbb6 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -120,7 +120,7 @@ This can include the compiler vendor and version used to build the application. When using a new language or standard library feature, circumstances may warrant doing so while maintaining an existing [MSRV](https://doc.rust-lang.org/cargo/reference/rust-version.html), -rather than raising to what the language or standard library feature needs. +rather than raising it to what the language or standard library feature needs. This can be accomplished by conditionally compiling the code for that feature. As its hard to talk about features and versions in the future, From 955a11e7055c56d98d047476f4630fbac1dadd6e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:41:40 -0500 Subject: [PATCH 070/109] fix(ref): Remove a line from before bad string literals errored --- text/3857-cfg-version.md | 1 - 1 file changed, 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 6778b90cbb6..9946a607117 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -213,7 +213,6 @@ ConfigurationSince -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_L When evaluating `since`, 1. If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, the compiler will error - *(for warning on always-false checks, see `--check-cfg`)* 2. If `IDENTIFIER` is unset, this will evaluate to `false`. 3. If any of the following evaluates to `true` for any cfg entry for `IDENTIFIER`, `since` will evaluate to `true`, otherwise `false`. 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. From 3e89ae881d6748bb3e1cb40b7186e37b02f4b1a8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:44:17 -0500 Subject: [PATCH 071/109] fix(ref): Delegate to SemVer, rather than Cargo, on comparisons --- text/3857-cfg-version.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 9946a607117..37f38ffc559 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -212,15 +212,14 @@ ConfigurationSince -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_L ``` When evaluating `since`, -1. If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, the compiler will error +1. If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, the compiler will error. Unset `` and `` will assumed to be `0`. 2. If `IDENTIFIER` is unset, this will evaluate to `false`. 3. If any of the following evaluates to `true` for any cfg entry for `IDENTIFIER`, `since` will evaluate to `true`, otherwise `false`. 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, the compiler will error Note that this excludes support for the `+build` field. - 3. Otherwise, the `IDENTIFIER`s value will be compared to the string literal according to -[Cargo's `>=` version requirements](https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#comparison-requirements). - For example, `#[cfg(since(rust, "1.90"))]` will be treated as `1.95.2 >= 1.90.0`. + 3. Otherwise, if `IDENTIFIER`s value has the same or higher [precedence](https://semver.org/#spec-item-11), this entry will evaluate to `true` + For example, `#[cfg(since(rust, "1.90"))]` will be interpreted as `prededence_of(1.95.2) >= precedence_of(1.90.0)`. Examples: - `cfg(since(unset_name, "1.0.0"))` will be false From e5440161ef566d47df4990525c891929f31b78fa Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:47:32 -0500 Subject: [PATCH 072/109] fix(ref): Clarify use of +build --- text/3857-cfg-version.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 37f38ffc559..4a6d8b4cba7 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -213,11 +213,11 @@ ConfigurationSince -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_L When evaluating `since`, 1. If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, the compiler will error. Unset `` and `` will assumed to be `0`. + Note that this excludes support for the `+build` field. 2. If `IDENTIFIER` is unset, this will evaluate to `false`. 3. If any of the following evaluates to `true` for any cfg entry for `IDENTIFIER`, `since` will evaluate to `true`, otherwise `false`. 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. - 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, the compiler will error - Note that this excludes support for the `+build` field. + 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, minus the `+build` field, the compiler will error. 3. Otherwise, if `IDENTIFIER`s value has the same or higher [precedence](https://semver.org/#spec-item-11), this entry will evaluate to `true` For example, `#[cfg(since(rust, "1.90"))]` will be interpreted as `prededence_of(1.95.2) >= precedence_of(1.90.0)`. @@ -377,6 +377,9 @@ For instance, in the [future-possibilities] we go so far as to allow alphabetic This can have side effects like allowing comparing words like with `#[cfg(since(hello, "world"))]`, whether intended by the users (potential abuse of the feature) or not (masking errors that could help find bugs). +Deferring `+build` metadata field support a non-precedence setting field can cause confusion (as shown in Cargo/crates.io), +its likely best to hold off for us to evaluate the use of it when the need arrives. + If we were stricter on the syntax, we could allow for version numbers to be directly accepted, without quotes (e.g. `#[cfg(since(rust, 1.95.0))]`). @@ -811,8 +814,8 @@ As the ecosystem grows and matures, the Rust language and standard library may not be the only dependencies users wish to support multiple versions of. We may want to allow `#(cfg(since(serde, "1.0.900")]`. -As dependency versions can have build numbers, -we'd need to decide whether to further relax version numbers by allowing build numbers +As dependency versions can have a `+build` metadata field, +we'd need to decide whether to further relax version numbers by allowing a `+build` metadata field which would not affect precedence or whether the caller is responsible for stripping them, losing potential release information. From 3e632bf117bf25ccc029d3f7f868fac3e64567d4 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:53:42 -0500 Subject: [PATCH 073/109] fix(rationale): Further clarify +build --- text/3857-cfg-version.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 4a6d8b4cba7..09d039c0c39 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -377,8 +377,9 @@ For instance, in the [future-possibilities] we go so far as to allow alphabetic This can have side effects like allowing comparing words like with `#[cfg(since(hello, "world"))]`, whether intended by the users (potential abuse of the feature) or not (masking errors that could help find bugs). -Deferring `+build` metadata field support a non-precedence setting field can cause confusion (as shown in Cargo/crates.io), +Deferring `+build` metadata field support for `IDENTIFIER`s value because a non-precedence setting field can cause confusion (as shown in Cargo/crates.io), its likely best to hold off for us to evaluate the use of it when the need arrives. +Like with Cargo, the `+build` metadata field should probably not be supported in the string literal (version requirement) because it does not affect precedence. If we were stricter on the syntax, we could allow for version numbers to be directly accepted, without quotes From 5b9fefe30dc9c4541d9cef4c293f684367b93853 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:54:56 -0500 Subject: [PATCH 074/109] fix(ref): Be consistent with +build --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 09d039c0c39..151e34541aa 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -241,7 +241,7 @@ A new predicate will be added of the form: CheckConfigurationSince -> `since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` ``` -The syntax for the contents of the string literal is a SemVer value without the build field. +The syntax for the contents of the string literal is a SemVer value without the `+build` metadata field. This will specify that for the given cfg, literals will be valid if: - SemVer syntax From 102480817127c64a3d1425b09aaacd59949232b9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:56:26 -0500 Subject: [PATCH 075/109] fix(rationale): Explain lack of +build for check-cfg --- text/3857-cfg-version.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 151e34541aa..1b966feecca 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -411,6 +411,9 @@ The `--check-cfg` predicate and the value for `rust` ensures users get warnings in case we want the future possibility of relaxing SemVer versions *and* we want to infer from the fields used in `--check-cfg` to specify the maximum number of fields accepted in comparisons. +Like with the cfg's string literal, +check-cfg's string literal does not support the `+build` metadata field as it has no affect on precedence. + We could have the check-cfg `since` predicate only apply to the cfg `since` predicate, causing `#[cfg(rust = "1.100.0")]` to warn. However, From 40316270cf06bfec7983f71da795109201a31634 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 13:57:48 -0500 Subject: [PATCH 076/109] fix(ref): Be consistent with string literal --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 1b966feecca..2463c919d4a 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -243,7 +243,7 @@ CheckConfigurationSince -> `since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) ` The syntax for the contents of the string literal is a SemVer value without the `+build` metadata field. -This will specify that for the given cfg, literals will be valid if: +This will specify that for the given cfg, string literals will be valid if: - SemVer syntax - from the specified version and up From 2bbed7893a1a0cf361c129f1d6c692ebe132e5d5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 14:05:25 -0500 Subject: [PATCH 077/109] fix(ref): Clarify unexpected_cfgs --- text/3857-cfg-version.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 2463c919d4a..1c52ab27a34 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -251,15 +251,17 @@ Specifically when the given cfg is used with the cfg `since` predicate: - the string literal should not be of a syntax that evaluates to `false` - the string literal must be a minimum version requirement that specifies a subset of what the `--check-cfg` specifies +This composes with all other values specified with the `values()` predicate + So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, - ✅ `#[cfg(foo = "1.100.0")]` -- ⚠️ `#[cfg(foo = "1.0")]`: not SemVer syntax +- ⚠️ `#[cfg(foo = "1.100")]`: not SemVer syntax - ✅ `#[cfg(since(foo, "1.95.0"))]` - ✅ `#[cfg(since(foo, "1.100.0"))]` - ✅ `#[cfg(since(foo, "3.0.0"))]` - ✅ `#[cfg(since(foo, "1.95"))]` -- ⚠️ `#[cfg(since(foo, "1.90.0"))]`: matches a superset of `--check-cfg` - ⚠️ `#[cfg(since(foo, "1.95.0-0"))]`: matches a superset of `--check-cfg` +- ⚠️ `#[cfg(since(foo, "1.90.0"))]`: matches a superset of `--check-cfg` - ⚠️ `#[cfg(since(foo, "1"))]`: matches a superset of `--check-cfg` - ⚠️ `#[cfg(since(foo, "bar"))]`: invalid string literal syntax From 56305c3b1d6e0a954ed9430e9851e657b023a97c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 14:06:36 -0500 Subject: [PATCH 078/109] fix(unresolved): Discusss pre-release name --- text/3857-cfg-version.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 1c52ab27a34..093b42ee277 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -701,6 +701,7 @@ Haskell: - How strict should the version syntax be at this stage? - `since(rust, "1.95")`, `version(rust, ">=1.95")`, or `version(rust >= "1.95")` - Is `"1.95.0-incomplete"` an acceptable compromise on the question of whether to treat nightlies as complete or incomplete? + - How much do we care about the name? # Future possibilities [future-possibilities]: #future-possibilities From 7561a723267fa0387439b8a6c1b2f5e75dfb2b3c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 14:08:29 -0500 Subject: [PATCH 079/109] fix(drawback): Add findings on use of --cfg rust --- text/3857-cfg-version.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 093b42ee277..214c10538ac 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -301,6 +301,9 @@ Clippy may wish to: People may be using `--cfg rust` already and would be broken by this change. There are no compatibility concerns with predicate names. +At least a preliminary search of GitHub did not uncover uses +but that search may have been incomplete +and that data set is biased towards open source and not all uses of Rust. `#[cfg(not(since("1.95.0")))]` is unnatural grammar when read out loud and could cause confusion. This could be helped by supporting a `#[cfg(before("1.95.0"))]`. From fed559bac39a6dd73e5a82653e80be0e8eca7bc1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 14:11:33 -0500 Subject: [PATCH 080/109] fix(drawback): Add context to the not(since) concern --- text/3857-cfg-version.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 214c10538ac..dc8829a8b63 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -305,8 +305,11 @@ At least a preliminary search of GitHub did not uncover uses but that search may have been incomplete and that data set is biased towards open source and not all uses of Rust. -`#[cfg(not(since("1.95.0")))]` is unnatural grammar when read out loud and could cause confusion. -This could be helped by supporting a `#[cfg(before("1.95.0"))]`. +Ignoring the logic, a straight-English reading of `#[cfg(not(since("1.95.0")))]` is unnatural and could cause confusion. +This can be mitigated by use of `#[cfg_alias]` +which will let users provide a semantic name for the positive case that works with the negative case, +on top of the other benefits of providing a central, semantic name. +This could also be helped by supporting a `#[cfg(before("1.95.0"))]`. This was left to [a future possibility][future-possibilities]. While Rust can stacks `cfg`s to test for the presence of this feature on older version, From 459c87b9c5e4d6cd7f3c3be24995f725cc795732 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 14:11:55 -0500 Subject: [PATCH 081/109] style(drawback): Fix grammar --- text/3857-cfg-version.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index dc8829a8b63..445620b1987 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -312,10 +312,10 @@ on top of the other benefits of providing a central, semantic name. This could also be helped by supporting a `#[cfg(before("1.95.0"))]`. This was left to [a future possibility][future-possibilities]. -While Rust can stacks `cfg`s to test for the presence of this feature on older version, +While Rust can stacks `cfg`s to test for the presence of this feature on older versions, this does not include a solution for adopting this within `Cargo.toml` without waiting for an MSRV bump to the version `since` is stabilized in. -Traditionally, maintainers only test their MSRV and latest, assuming those will catch every issue. +Traditionally, maintainers only test their MSRV and latest stable, assuming those will catch every issue. While that isn't always true today (e.g. some Cargo features go from "unknown" warning to "unstable" error to supported and MSRV might be in the warning phase), having distinct implementations for different Rust versions can make the testing matrix more complex. Tools like [`cargo hack`](https://crates.io/crates/cargo-hack) can help which can run commands on not just one toolchain version but also the every version starting with the MSRV with a command like `cargo hack --rust-version --version-step 1 check`. From dbbcab489b6ad55b18780cd1755d692219cd08c0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 19 Sep 2025 14:13:55 -0500 Subject: [PATCH 082/109] style(drawback): Fill in assumed gaps about generality --- text/3857-cfg-version.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 445620b1987..23927f2dff1 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -326,8 +326,8 @@ this does not cover the use case from [rustversion](https://crates.io/crates/rus Libraries could having ticking time bombs that accidentally break or have undesired behavior for some future Rust version that can't be found until we hit that version. -Compared to the alternative designs, -this may take more time in design discussions, implementation, and vetting the implementation +Compared to the more specialized alternative designs, +this more general solution may take more time in design discussions, implementation, and vetting the implementation as there are more corner cases to cover, particularly with how this integrates with future possibilities. ## Pre-releases for major versions From bc71f84f2575e623407b6e1d754b41be56329964 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 20 Sep 2025 20:37:48 -0500 Subject: [PATCH 083/109] feat(alt): Cover version(since) --- text/3857-cfg-version.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 23927f2dff1..9ca6e1daa64 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -527,16 +527,17 @@ so we do not include that information. ### `version(rust, ">=1.95")` Instead of having an assumed operator for the predicate, -we could require an operator as either: +we could require an operator or predicate as either: - `version(rust, ">=1.95")` - `version(rust >= "1.95")` +- `version(rust, since("1.95"))` For Cargo, operators do not match pre-release versions unless the operand uses them though this may be relaxed, see [cargo#14305](https://github.com/rust-lang/cargo/pull/14305). This does not fit with out use cases because it causes discontinuities while users of the `cfg` need continuity. -This allows moving to a more specialized predicate name than `since` without losing the conveyed meaning. +This allows moving to a more specialized outer predicate name than `since` without losing the conveyed meaning. If the operator is outside of the string literal - we could also make it a bare word but that could lead to problems when dealing with relaxing of the version syntax @@ -548,6 +549,17 @@ If the operator is inside the string literal - users may stumble and be frustrated with missing features from cargo (do we include all unary and binary operators?) - behavior differences with Cargo may be needed due to different use cases but could lead to user bugs and frustration as it might not match what users are familiar with +If we nest `since` inside `version`, +- If there is a concern with boundary with `since` conditions that aren't alleviated by the discussion else where, + then this isn't helped because we are still using `since` +- Its not clear how a user is expected to reason about this (i.e. how do we teach this?) + especially in light of how the existing predicates work +- This creates a DSL inside our existing DSL which feels tacked on like using [rustversion](https://crates.io/crates/rustversion) +- Users are likely to hit impedance mismatches between principles they expect to work within the parent DSL and this DSL (e.g. using `all`) +- Nesting APIs puts more of a burden on the user, their editing experience, and our documentation structure to navigate compared to a flat structure + - If this is just to make the name `since` more specific, + we could just as well be served by naming it `version_since` + ### `cfg(rust_version(1.95))` *(this is [RFC 2523](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html))* @@ -705,7 +717,7 @@ Haskell: - `rust` or `rust_version`? - `--cfg rust` or `--cfg has_rust` for using now without an MSRV bump? - How strict should the version syntax be at this stage? -- `since(rust, "1.95")`, `version(rust, ">=1.95")`, or `version(rust >= "1.95")` +- `since(rust, "1.95")`, `version_since(rust, "1.95")`, `version(rust, ">=1.95")`, `version(rust >= "1.95")`, or `version(rust, since("1.95"))` - Is `"1.95.0-incomplete"` an acceptable compromise on the question of whether to treat nightlies as complete or incomplete? - How much do we care about the name? From 94d2438e4cb6cd443c2c95fe8b012dd6ed4ce9e4 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 20 Sep 2025 20:38:01 -0500 Subject: [PATCH 084/109] style: Fix typo --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 9ca6e1daa64..1cd5c20b70e 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -219,7 +219,7 @@ When evaluating `since`, 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, minus the `+build` field, the compiler will error. 3. Otherwise, if `IDENTIFIER`s value has the same or higher [precedence](https://semver.org/#spec-item-11), this entry will evaluate to `true` - For example, `#[cfg(since(rust, "1.90"))]` will be interpreted as `prededence_of(1.95.2) >= precedence_of(1.90.0)`. + For example, `#[cfg(since(rust, "1.90"))]` will be interpreted as `precedence_of(1.95.2) >= precedence_of(1.90.0)`. Examples: - `cfg(since(unset_name, "1.0.0"))` will be false From b3b7c7606820ad89415e7d8a82769ace967e5b77 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Sep 2025 12:23:30 -0500 Subject: [PATCH 085/109] style: Grammar Co-authored-by: Jubilee --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 1cd5c20b70e..4504b8f19e1 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -552,7 +552,7 @@ If the operator is inside the string literal If we nest `since` inside `version`, - If there is a concern with boundary with `since` conditions that aren't alleviated by the discussion else where, then this isn't helped because we are still using `since` -- Its not clear how a user is expected to reason about this (i.e. how do we teach this?) +- It's not clear how a user is expected to reason about this (i.e. how do we teach this?) especially in light of how the existing predicates work - This creates a DSL inside our existing DSL which feels tacked on like using [rustversion](https://crates.io/crates/rustversion) - Users are likely to hit impedance mismatches between principles they expect to work within the parent DSL and this DSL (e.g. using `all`) From be317a151f8bf52828f67609fd3c1ab85f867603 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Sep 2025 12:23:44 -0500 Subject: [PATCH 086/109] style: Grammar Co-authored-by: Jubilee --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 4504b8f19e1..2f8064a200e 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -550,7 +550,7 @@ If the operator is inside the string literal - behavior differences with Cargo may be needed due to different use cases but could lead to user bugs and frustration as it might not match what users are familiar with If we nest `since` inside `version`, -- If there is a concern with boundary with `since` conditions that aren't alleviated by the discussion else where, +- If there is a concern with boundary with `since` conditions that aren't alleviated by the discussion elsewhere, then this isn't helped because we are still using `since` - It's not clear how a user is expected to reason about this (i.e. how do we teach this?) especially in light of how the existing predicates work From 851d2ed49d3e0cf2d2a23ad3d01f7b38c43d7d91 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Sep 2025 19:39:34 -0500 Subject: [PATCH 087/109] fix(ref): Remove stale reference to invalid string literals being false --- text/3857-cfg-version.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 2f8064a200e..41c6a1389de 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -247,10 +247,11 @@ This will specify that for the given cfg, string literals will be valid if: - SemVer syntax - from the specified version and up -Specifically when the given cfg is used with the cfg `since` predicate: -- the string literal should not be of a syntax that evaluates to `false` +When checking a `since` predicate, - the string literal must be a minimum version requirement that specifies a subset of what the `--check-cfg` specifies +*note: non-version string literals are already a compiler error* + This composes with all other values specified with the `values()` predicate So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, From f461c5be2be20049d0ea1adf47af470e5bc22fa2 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Sep 2025 19:41:45 -0500 Subject: [PATCH 088/109] feat(unresolved): Call out decision on betas and incomplete --- text/3857-cfg-version.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 41c6a1389de..87506ec48ed 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -721,6 +721,7 @@ Haskell: - `since(rust, "1.95")`, `version_since(rust, "1.95")`, `version(rust, ">=1.95")`, `version(rust >= "1.95")`, or `version(rust, since("1.95"))` - Is `"1.95.0-incomplete"` an acceptable compromise on the question of whether to treat nightlies as complete or incomplete? - How much do we care about the name? + - Are beta's incomplete? Stricty speaking, yes. However, in most cases they will be complete. # Future possibilities [future-possibilities]: #future-possibilities From 102adb10a957eb02746369d0187517efd047cad0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Sep 2025 19:44:56 -0500 Subject: [PATCH 089/109] fix: Use shorthand for values(none()) --- text/3857-cfg-version.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 87506ec48ed..8b11d386ce5 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -180,7 +180,7 @@ fn main() { However, this would produce an `unexpected_cfgs` lint and you would need to add the following to `Cargo.toml`: ```toml [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust)'] } ``` Say you were wanting to test out `#[must_use]` after it got stabilized on nightly to provide feedback and to be ready for when it hits stable, @@ -474,7 +474,7 @@ fn is_stderr_terminal() -> bool { To allow checking for the presence of `rust`, add the following to your `Cargo.toml`: ```toml [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust,values(none()))'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust)'] } ``` Alternatively, we could have the built-in `--check-cfg` for `rust` include `values(none())` but: - When building on an old version, users will see the warning and will likely want to add it anyways. From 83a393c403c824c5ca628618447238b604d85334 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Sep 2025 19:47:36 -0500 Subject: [PATCH 090/109] fix(unresolved): Decide on values(none()) --- text/3857-cfg-version.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 8b11d386ce5..43ece3b2976 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -717,6 +717,7 @@ Haskell: - `rust` or `rust_version`? - `--cfg rust` or `--cfg has_rust` for using now without an MSRV bump? + - Should the `check-cfg` include `values(none())` or not? - How strict should the version syntax be at this stage? - `since(rust, "1.95")`, `version_since(rust, "1.95")`, `version(rust, ">=1.95")`, `version(rust >= "1.95")`, or `version(rust, since("1.95"))` - Is `"1.95.0-incomplete"` an acceptable compromise on the question of whether to treat nightlies as complete or incomplete? From fb896af9f70af0e56d4d2b2025668cd1ef7fe21b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Sep 2025 19:48:44 -0500 Subject: [PATCH 091/109] feat(rationale): Cover more trade offs for `values(none())` --- text/3857-cfg-version.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 43ece3b2976..7c1d28f1918 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -480,7 +480,8 @@ Alternatively, we could have the built-in `--check-cfg` for `rust` include `valu - When building on an old version, users will see the warning and will likely want to add it anyways. - We lose out on `--check-cfg` identifying misuses. Instead, we may wish to add a dedicated predicate intended for "is set". - +- The lint is an opportunity to tell people how to suppress it in old versions +- However, this does "punish" people who need it but don't care about warnings on old versions ### Pre-release From 18a9594421143f1bdc5765a3eb05841d9bd2ae8a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Sep 2025 19:56:02 -0500 Subject: [PATCH 092/109] style: Fix spelling --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 7c1d28f1918..b0d8041e5a1 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -826,7 +826,7 @@ we could have the following definition, building on the above relaxing of SemVer Instead of defining a new `#[cfg]` predicate, [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750) could reuse the `#[cfg(since)]` predicate. -Building on the above relaxing of Semver, we should meat the needs of most versioning systems. +Building on the above relaxing of Semver, we should meet the needs of most versioning systems. The one known exception is "post releases" (e.g. [`1.2.0.post1`](https://packaging.python.org/en/latest/discussions/versioning/) which, if we translated it to SemVer's syntax of `1.2.0-post1`, would be treated as a pre-release. From f3b4a6948a2c6bd97569c5dd165057472ecef4b0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Sep 2025 19:56:51 -0500 Subject: [PATCH 093/109] fix(future): Correct sorting of post releases --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index b0d8041e5a1..e481126e404 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -830,7 +830,7 @@ Building on the above relaxing of Semver, we should meet the needs of most versi The one known exception is "post releases" (e.g. [`1.2.0.post1`](https://packaging.python.org/en/latest/discussions/versioning/) which, if we translated it to SemVer's syntax of `1.2.0-post1`, would be treated as a pre-release. -We can translate this to extra precision, e.g. `1.2.0-post1` could be `1.2.0.post1`. +We can translate this to extra precision, e.g. `1.2.0-post1` could be `1.2.0.post.1`. This would require the check-cfg `since` to use the appropriate amount of precision to not warn. If this is still not sufficient, we some options include: From ae5822ccdd023397b9fa45b8ad7d4bdd974435ff Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 08:40:06 -0500 Subject: [PATCH 094/109] fix(rationale): Clarify about since name --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index e481126e404..ca600292e8f 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -366,7 +366,7 @@ we need to consider cases like `version(python, "2.8")` and whether people would We could also call this `minimum`, or support comparison operators in the spirit of [RFC 3796](https://github.com/rust-lang/rfcs/pull/3796). The risk with a general word like `since` is if we gain support for other data types in cfgs, like integers for embedded development. The name `since` might apply in some situations but not others and its unclear if we'd want to generalize it past versions. -While having a specific name avoids these concerns. +Having a more specific name like `version_since` / `since_version` could avoid these concerns. We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(since("1.95"))]` as a shorthand. However, this would look confusing in Cargo and doesn't seem like its offering enough of a benefit to be worth the costs. From 6b7ec78a69be8c3bee48ca0450005c8d31533529 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 08:45:08 -0500 Subject: [PATCH 095/109] feat(rationale): Cover since evaluting to false --- text/3857-cfg-version.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index ca600292e8f..c4ab814aee7 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -380,6 +380,13 @@ This does leave the door open for us to relax this in the future once we become Alternatively, we could try to determine a flexible-enough version syntax now though that comes with the risk that it isn't sufficient. Another benefit to erroring is so `not(since(invalid, ""))` is not `true`. +Having a unset or name-only `IDENTIFIER` evaluate to `false` is consistent with `cfg(IDENTIFIER)` and `cfg(IDENTIFIER = "value")`. +When a version can be conditionally present, +it avoids the need to gate an expression which would either require including `--cfg IDENTIFIER` with `--cfg IDENTIFIER=""` (like `--cfg rust`) to check for its presence or for us to add an `is_set` predicate. +However, this would also apply to a `before` predicate, making `before` not the same as `not(since)`. +If we did error on unset or name-only `IDENTIFIER`s, +we'd need it to be done lazily so as to not error if the expression is gated. + Deferring the more flexible syntax avoids having to couple this decision to what syntax should be allowed which will allow us to better evaluate the ramifications for each time we relax things. For instance, in the [future-possibilities] we go so far as to allow alphabetic characters in any field while making the precision arbitrary. From 1589434bed0481ea2719da823824b01b5a4b8aef Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 08:57:21 -0500 Subject: [PATCH 096/109] feat(alt): Add unresolved question for operators in version() --- text/3857-cfg-version.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index c4ab814aee7..fa9d769b4c6 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -552,6 +552,7 @@ If the operator is outside of the string literal - we could also make it a bare word but that could lead to problems when dealing with relaxing of the version syntax - this creates a DSL inside our existing DSL which feels tacked on like using [rustversion](https://crates.io/crates/rustversion) - We'd need to decide how far to extend this DSL +- We have not considered the syntax implications for check-cfg which would not have a left-hand side for the operator. If the operator is inside the string literal - this would feel comfortably familiar due to Cargo From cab1e9381e260c5cf87f898adfaa876f4b93d1c5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 09:07:46 -0500 Subject: [PATCH 097/109] style(unresolved): Fix spelling --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index fa9d769b4c6..17502fa042d 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -731,7 +731,7 @@ Haskell: - `since(rust, "1.95")`, `version_since(rust, "1.95")`, `version(rust, ">=1.95")`, `version(rust >= "1.95")`, or `version(rust, since("1.95"))` - Is `"1.95.0-incomplete"` an acceptable compromise on the question of whether to treat nightlies as complete or incomplete? - How much do we care about the name? - - Are beta's incomplete? Stricty speaking, yes. However, in most cases they will be complete. + - Are beta's incomplete? Strictly speaking, yes. However, in most cases they will be complete. # Future possibilities [future-possibilities]: #future-possibilities From 674ec04db98d48315c98dbe3cbdb009425017498 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 09:09:40 -0500 Subject: [PATCH 098/109] feat(alt): Cover typing/operator overloading --- text/3857-cfg-version.md | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 17502fa042d..d1b7b8cecd0 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -533,6 +533,65 @@ so we do not include that information. ## Alternative designs +### `cfg(rust >= "1.95")` + +[RFC #3796](https://github.com/rust-lang/rfcs/pull/3796) +will be allowing operators in addition to predicates and it stands to reason that we can extend that +to version comparisons as well. + +The expression `rust >= "1.95"` without any other changes would be a string comparison and not a version precedence comparison. +We'd need to add the concept of types to cfg. +We could make check-cfg load-bearing by relying on its type information +or we could add coercion functions to cfg. + +So given `--cfg=rust --cfg=rust=version("1.95.0")`, you could do `cfg(rust >= version("1.95"))`. + +With typing, +`cfg_values!` (a future possibility) could evaluate to the given type. +So for `--cfg foo=integer("1')`, `cfg_value!(foo)` would be as if you typed `1`. +For versions, +as there is no native Rust type, +we'd likely have it evaluate to a `&'static str`. + +[RFC #3796](https://github.com/rust-lang/rfcs/pull/3796) +does not address questions around binary operators, +requiring us to work it out. +For example, are the sides of the operator fully swappable? +If we define all comparisons, would `==` be different than `=`? +How will these operators work in the presence of multiple values or a name-only cfg? + +Would we allow implicit coercion so you can skip the `version` inside of `cfg`, like `cfg(rust >= "1.95")`? +I would suggest not because this would make it harder to catch bugs where +- The `--cfg` is not a version but you thought it was +- The `--cfg` should be a version but `version()` was left off + +Currently, check-cfg does not apply at all to `--cfg` because it is commonly used with `RUSTFLAGS` which +are applied to all packages and would warn that an unknown `IDENTIFIER` is in use for packages that don't care. +We could still skip checking for unknown `IDENTIFIER`s and instead warn on misuse of `IDENTIFIER`s which would increase the chance of catching a mistake (unless a person duplicated there `--cfg` mistake with `--check-cfg`. + +Another is how to handle check-cfg. +The proposed syntax is a binary operator but there is no left-hand side in check-cfg. +Would we accept `cfg(rust, values(>="1.95"))`? +How would we specify types? Would we replace `values` with `versions`? + +Adding typing to cfg, +while likely something we'll do one day, +greatly enlarges the scope of this RFC. +This makes it harder to properly evaluate each part, +making it more likely we'll make mistakes. +This further delays the feature as the unstable period is likely to be longer. +We also are not ready to evaluate other use cases for typing to evaluate the impact +and likely won't until we move forward with [global features](https://internals.rust-lang.org/t/pre-rfc-mutually-excusive-global-features/19618) +and `cfg_values!`, +allowing us to cover use cases like embedded using [toml_cfg](https://crates.io/crates/toml-cfg). + +If we defer typing, we'll have to allow implicit coercion of values so we can mark `rust` as a version in the future without it being a breaking change. + +If we consider typing the correct long term solution but defer it, +we may want to consider the most narrowly scoped solution in the short term, +like `rust_version("1.95")`. +These "big questions" can then have dedicated issues and versions can be built on top of that. + ### `version(rust, ">=1.95")` Instead of having an assumed operator for the predicate, From be94add862e1cab79c14fb1e2fc369ebb1b66d49 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 11:28:38 -0500 Subject: [PATCH 099/109] fix(alt): Alude to implicit type support --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index d1b7b8cecd0..75e9aa5b9eb 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -548,7 +548,7 @@ So given `--cfg=rust --cfg=rust=version("1.95.0")`, you could do `cfg(rust >= ve With typing, `cfg_values!` (a future possibility) could evaluate to the given type. -So for `--cfg foo=integer("1')`, `cfg_value!(foo)` would be as if you typed `1`. +So for `--cfg foo=integer("1')` (maybe even `--cfg foo=1`), `cfg_value!(foo)` would be as if you typed `1`. For versions, as there is no native Rust type, we'd likely have it evaluate to a `&'static str`. From 79f50aab8b3e0938af4a471716e4a021308afa6c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 11:32:46 -0500 Subject: [PATCH 100/109] feat(alt): Bring up possible check-cfg syntax --- text/3857-cfg-version.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 75e9aa5b9eb..ea3c5e57c74 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -573,6 +573,8 @@ Another is how to handle check-cfg. The proposed syntax is a binary operator but there is no left-hand side in check-cfg. Would we accept `cfg(rust, values(>="1.95"))`? How would we specify types? Would we replace `values` with `versions`? +Or do we deviate from the check-cfg syntax and go with `cfg(rust >= version("1.95"))`? +This would make editions `--check-cfg 'cfg(edition, values(version("2015"), version("2018"), version("2021"), version("2024")))' --check-cfg 'cfg(edition >= verison("2025"))'` Adding typing to cfg, while likely something we'll do one day, From 3f765c17583ff1a3a2642db3c53fa3db5a22dfb4 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 13:11:25 -0500 Subject: [PATCH 101/109] feat(future): Clippy lint to use since with cfg_alias --- text/3857-cfg-version.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index ea3c5e57c74..6f3531571f8 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -976,3 +976,10 @@ There isn't a way to check if a `cfg` name is set, whether with or without value which would work like a `cfg` version of [`cfg_accessible`](https://dev-doc.rust-lang.org/stable/unstable-book/library-features/cfg-accessible.html) so long as the `cfg` is unconditionally set. + +## Clippy lint: prefer using `cfg(since)` via `cfg_alias!` + +Once we have this RFC and [RFC 3804](https://github.com/rust-lang/rfcs/pull/3804), +we may want a restriction lint that would encourage people to use `cfg(since)` through `cfg_alias` +so people work off of names in `cfg()` throughout their code base, +rather than versions that may be duplicated and lack semantic meaning. From 56ddfc3daa65e06983e672bb7e3e700fb63d8c10 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 13:46:46 -0500 Subject: [PATCH 102/109] fix(ref): Nightly and beta are incomplete --- text/3857-cfg-version.md | 53 +++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 6f3531571f8..6b85bb96520 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -183,21 +183,6 @@ However, this would produce an `unexpected_cfgs` lint and you would need to add unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust)'] } ``` -Say you were wanting to test out `#[must_use]` after it got stabilized on nightly to provide feedback and to be ready for when it hits stable, -you would instead use `"1.27.0-0"` to match all pre-release versions of 1.27.0: -```rust -#[cfg_attr(since(rust, "1.27.0-0"), must_use)] -fn double(x: i32) -> i32 { - 2 * x -} - -fn main() { - double(4); - // warning: unused return value of `double` which must be used - // ^--- This warning only happens if we are on Rust >= 1.27. -} -``` - # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -261,7 +246,6 @@ So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, - ✅ `#[cfg(since(foo, "1.100.0"))]` - ✅ `#[cfg(since(foo, "3.0.0"))]` - ✅ `#[cfg(since(foo, "1.95"))]` -- ⚠️ `#[cfg(since(foo, "1.95.0-0"))]`: matches a superset of `--check-cfg` - ⚠️ `#[cfg(since(foo, "1.90.0"))]`: matches a superset of `--check-cfg` - ⚠️ `#[cfg(since(foo, "1"))]`: matches a superset of `--check-cfg` - ⚠️ `#[cfg(since(foo, "bar"))]`: invalid string literal syntax @@ -271,9 +255,10 @@ So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, A new built-in cfg `--cfg=rust --cfg=rust=""` will be added by the compiler that specifies the language version. This will be the version of `rustc` with the behavior for pre-release versions being unspecified. -We expect rustc to: -- Translate the `-nightly` pre-release to `-incomplete` -- Strip the `-beta.5` pre-release +We expect rustc to treat beta and nightly versions as an "incomplete" implementation of that language version, +reporting some number less than the current nightly. +We could either track the latest patch release at the time of the nightly, assume `x.y.0`, or assume `x.y.99`. +The compiler may choose to offer an unstable flag to mark a nightly as "complete" to allow for testing of features with `since`. `rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95.0")))'` (or whatever version this gets stabilized in). @@ -517,7 +502,7 @@ For RFC 2523, they settled on pre-releases being incomplete, favoring maintainers to adopt stabilized-on-nightly features immediately while letting people on pinned nightlies or bisecting nightlies to set a `-Z` to mark the version as incomplete. -In this RFC, we settled on translating `-nightly` to `-incomplete` because: +Originally, this RFC chose to translate `-nightly` to `-incomplete` because: - Maintainers can adopt stabilized-on-nightly features with `#[cfg(since(rust, "1.100.0-0"))]` (the lowest pre-release for `1.100.0`), keeping friction low while explicitly acknowledging that the unstable feature may change - `-0` is recommended over `-incomplete` or any other value as the exact pre-release value is unspecified. - Allows build scripts to experiment with other logic when approximating the vendor version from the language version with less of a chance of needing to invoke `rustc` (e.g. detecting nightly) @@ -531,6 +516,9 @@ As for differentiating between nightlies, that corresponds more to the vendor version than the language version, so we do not include that information. +However, we've decided to punt on the question of nightlies to reduce the scope of this RFC +and out of concern for published packages using unstable features that will automatically get enabled somehow. + ## Alternative designs ### `cfg(rust >= "1.95")` @@ -874,6 +862,31 @@ We could always relax this incrementally, e.g. - `BuildMetadata` for dependency versions - Whatever `target_version` requires +## Incomplete language versions + +Rustc could indicate that it implements an incomplete version of the compiler by having an `-incomplete` pre-release field. +This would be used for nightlies and there would be a question of whether beta should be incomplete or not. + +This can be done later as its unstable. + +A guide-level explanation would be: + +> Say you were wanting to test out `#[must_use]` after it got stabilized on nightly to provide feedback and to be ready +> for when it hits stable, +> you would instead use `"1.27.0-0"` to match all pre-release versions of 1.27.0: +> ```rust +> #[cfg_attr(since(rust, "1.27.0-0"), must_use)] +> fn double(x: i32) -> i32 { +> 2 * x +> } +> +> fn main() { +> double(4); +> // warning: unused return value of `double` which must be used +> // ^--- This warning only happens if we are on Rust >= 1.27. +> } +> ``` + ## `--cfg edition` In adding a `cfg` for the Edition, we could model it as either: From 3519b837f4895c9d794c575676b8c02580dd023c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 15:01:32 -0500 Subject: [PATCH 103/109] style: Fix typo --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 6b85bb96520..30a6511f909 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -562,7 +562,7 @@ The proposed syntax is a binary operator but there is no left-hand side in check Would we accept `cfg(rust, values(>="1.95"))`? How would we specify types? Would we replace `values` with `versions`? Or do we deviate from the check-cfg syntax and go with `cfg(rust >= version("1.95"))`? -This would make editions `--check-cfg 'cfg(edition, values(version("2015"), version("2018"), version("2021"), version("2024")))' --check-cfg 'cfg(edition >= verison("2025"))'` +This would make editions `--check-cfg 'cfg(edition, values(version("2015"), version("2018"), version("2021"), version("2024")))' --check-cfg 'cfg(edition >= version("2025"))'` Adding typing to cfg, while likely something we'll do one day, From b3bff0f58ade193f14c528c1984a6daf85dd6776 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 15:05:33 -0500 Subject: [PATCH 104/109] fix(ref): Drop patch, specify Version syntax and precedence --- text/3857-cfg-version.md | 149 ++++++++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 57 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 30a6511f909..dc4d907adcf 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -194,26 +194,41 @@ As Cargo mirrors Rust's `#[cfg]` syntax, it too will gain this predicate. The [syntax](https://doc.rust-lang.org/reference/conditional-compilation.html#grammar-ConfigurationPredicate) is: ``` ConfigurationSince -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` + +Version = NumericVersionField `.` NumericVersionField + +NumericVersionField -> + `0` + | ( [`1`..`9`] DEC_DIGIT* ) ``` When evaluating `since`, -1. If the string literal does not conform to the syntax from `` to `..-` where the first three fields must be integers, the compiler will error. Unset `` and `` will assumed to be `0`. - Note that this excludes support for the `+build` field. +1. If the string literal does not conform to the syntax `Version`, the compiler will error. 2. If `IDENTIFIER` is unset, this will evaluate to `false`. 3. If any of the following evaluates to `true` for any cfg entry for `IDENTIFIER`, `since` will evaluate to `true`, otherwise `false`. 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. - 2. If `IDENTIFIER`'s value is not a valid [SemVer](https://semver.org/) value, minus the `+build` field, the compiler will error. - 3. Otherwise, if `IDENTIFIER`s value has the same or higher [precedence](https://semver.org/#spec-item-11), this entry will evaluate to `true` + 2. If `IDENTIFIER`'s value is not a valid `Version`, the compiler will error. + 3. Otherwise, if `IDENTIFIER`s value has the same or higher precedence, this entry will evaluate to `true` For example, `#[cfg(since(rust, "1.90"))]` will be interpreted as `precedence_of(1.95.2) >= precedence_of(1.90.0)`. +With the precedence of: +- Precedence is calculated by separating the `Version` into the respective `NumericVersionField`s +- Precedence is determined by the first difference when comparing each field from left to right of `Version` + - `NumericVersionField` is compared numerically + +This was adopted from [SemVer](https://semver.org/) with the following changes: +- Removed pre-release +- Dropped down to 2 fields +- Removed build metadata (which does not affect precedence) + Examples: -- `cfg(since(unset_name, "1.0.0"))` will be false -- `--cfg name_only` and `cfg(since(name_only, "1.0.0"))` will be false -- `--cfg foo="bird"` and `cfg(since(name_only, "1.0.0"))` will be a compiler error -- `--cfg foo="1.1.0"` and `cfg(since(foo, "bird"))` will be a compiler error -- `--cfg foo="1.1.0"` and `cfg(since(foo, "1.0.0"))` will be true -- `--cfg foo="1.1.0"` and `cfg(since(foo, "1.2.0"))` will be false -- `--cfg foo --cfg foo="1.1.0" --cfg foo="1.0.0"` and `cfg(since(foo, "1.1.0"))` will be true +- `cfg(since(unset_name, "1.0"))` will be false +- `--cfg name_only` and `cfg(since(name_only, "1.0"))` will be false +- `--cfg foo="bird"` and `cfg(since(name_only, "1.0"))` will be a compiler error +- `--cfg foo="1.1"` and `cfg(since(foo, "bird"))` will be a compiler error +- `--cfg foo="1.1"` and `cfg(since(foo, "1.0"))` will be true +- `--cfg foo="1.1"` and `cfg(since(foo, "1.2"))` will be false +- `--cfg foo --cfg foo="1.1" --cfg foo="1.0"` and `cfg(since(foo, "1.1"))` will be true The compiler implementation currently treats cfgs as `HashSet<(String, Option)>` and would likely need to change this to `HashMap>>`` @@ -226,10 +241,10 @@ A new predicate will be added of the form: CheckConfigurationSince -> `since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` ``` -The syntax for the contents of the string literal is a SemVer value without the `+build` metadata field. +The syntax for the contents of the string literal is a `Version`. This will specify that for the given cfg, string literals will be valid if: -- SemVer syntax +- `Version` syntax - from the specified version and up When checking a `since` predicate, @@ -239,28 +254,26 @@ When checking a `since` predicate, This composes with all other values specified with the `values()` predicate -So given `--check-cfg 'cfg(foo, values(since("1.95.0")))'`, -- ✅ `#[cfg(foo = "1.100.0")]` -- ⚠️ `#[cfg(foo = "1.100")]`: not SemVer syntax -- ✅ `#[cfg(since(foo, "1.95.0"))]` -- ✅ `#[cfg(since(foo, "1.100.0"))]` -- ✅ `#[cfg(since(foo, "3.0.0"))]` +So given `--check-cfg 'cfg(foo, values(since("1.95")))'`, +- ✅ `#[cfg(foo = "1.100")]` +- ⚠️ `#[cfg(foo = "1.100.0")]`: not `Version` syntax +- ✅ `#[cfg(since(foo, "1.95"))]` +- ✅ `#[cfg(since(foo, "1.100"))]` +- ✅ `#[cfg(since(foo, "3.0"))]` - ✅ `#[cfg(since(foo, "1.95"))]` -- ⚠️ `#[cfg(since(foo, "1.90.0"))]`: matches a superset of `--check-cfg` -- ⚠️ `#[cfg(since(foo, "1"))]`: matches a superset of `--check-cfg` -- ⚠️ `#[cfg(since(foo, "bar"))]`: invalid string literal syntax +- ⚠️ `#[cfg(since(foo, "1.90"))]`: matches a superset of `--check-cfg` +- ⚠️ `#[cfg(since(foo, "1"))]`: not `Version` syntax +- ⚠️ `#[cfg(since(foo, "bar"))]`: not `Version` syntax ## `rust` cfg A new built-in cfg `--cfg=rust --cfg=rust=""` will be added by the compiler that specifies the language version. -This will be the version of `rustc` with the behavior for pre-release versions being unspecified. +This will be the version of `rustc` without the patch field and with the behavior for pre-release versions being unspecified. We expect rustc to treat beta and nightly versions as an "incomplete" implementation of that language version, reporting some number less than the current nightly. -We could either track the latest patch release at the time of the nightly, assume `x.y.0`, or assume `x.y.99`. -The compiler may choose to offer an unstable flag to mark a nightly as "complete" to allow for testing of features with `since`. -`rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95.0")))'` +`rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95")))'` (or whatever version this gets stabilized in). Like with `--check-cfg` for Cargo `features`, the compiler may choose to add additional context for why this lower bound is present (not stabilized). @@ -291,11 +304,11 @@ At least a preliminary search of GitHub did not uncover uses but that search may have been incomplete and that data set is biased towards open source and not all uses of Rust. -Ignoring the logic, a straight-English reading of `#[cfg(not(since("1.95.0")))]` is unnatural and could cause confusion. +Ignoring the logic, a straight-English reading of `#[cfg(not(since("1.95")))]` is unnatural and could cause confusion. This can be mitigated by use of `#[cfg_alias]` which will let users provide a semantic name for the positive case that works with the negative case, on top of the other benefits of providing a central, semantic name. -This could also be helped by supporting a `#[cfg(before("1.95.0"))]`. +This could also be helped by supporting a `#[cfg(before("1.95"))]`. This was left to [a future possibility][future-possibilities]. While Rust can stacks `cfg`s to test for the presence of this feature on older versions, @@ -310,6 +323,8 @@ As we don't expose a nightly's date, this does not cover the use case from [rustversion](https://crates.io/crates/rustversion) represented by `#[rustversion::since(2025-01-01)]`. +Without patch, there will be more difficulty in using the Language version as a proxy for the Vendor version for working around bugs or knowing what compiler version was used in a bug report. + Libraries could having ticking time bombs that accidentally break or have undesired behavior for some future Rust version that can't be found until we hit that version. Compared to the more specialized alternative designs, @@ -321,11 +336,11 @@ as there are more corner cases to cover, particularly with how this integrates w Pre-releases of major versions isn't a consideration for `rust` but in the general use of `since`. If wanting to split a continuous range with minor and patch versions, -`#[cfg(since(foo, "1.1.0"))]` and `#[cfg(not(since(foo, "1.1.0")))]` +`#[cfg(since(foo, "1.1"))]` and `#[cfg(not(since(foo, "1.1")))]` works reasonably well. The problem comes into play when doing so with major versions when pre-releases are involved, -like `#[cfg(since(foo, "2.0.0"))]` and `#[cfg(not(since(foo, "2.0.0")))]`. +like `#[cfg(since(foo, "2.0"))]` and `#[cfg(not(since(foo, "2.0")))]`. In this situation, a `2.0.0-dev.5` will match the second condition when the user likely only wanted to include `1.*`. Instead, they should do `#[cfg(since(foo, "2.0.0-0"))]` and `#[cfg(not(since(foo, "2.0.0-0")))]` or have a third case for pre-releases of `foo@2.0.0`. @@ -347,7 +362,7 @@ The `since` name was taken from This better conveys what operation is being performed than the original `version` name and leaves room for related predicates like `before`. In particular, as this is a general feature and not just for Rust version comparisons, -we need to consider cases like `version(python, "2.8")` and whether people would interpret that as an exact match, a SemVer match, or a `>=` match (the winner). +we need to consider cases like `version(python, "2.8")` and whether people would interpret that as an exact match, a SemVer compatible match (`^` in cargo), or a `>=` match (the winner). We could also call this `minimum`, or support comparison operators in the spirit of [RFC 3796](https://github.com/rust-lang/rfcs/pull/3796). The risk with a general word like `since` is if we gain support for other data types in cfgs, like integers for embedded development. The name `since` might apply in some situations but not others and its unclear if we'd want to generalize it past versions. @@ -356,7 +371,7 @@ Having a more specific name like `version_since` / `since_version` could avoid t We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(since("1.95"))]` as a shorthand. However, this would look confusing in Cargo and doesn't seem like its offering enough of a benefit to be worth the costs. -`ConfigurationSince` requires the `IDENTIFIER` and string literal to be a SemVer version, +`ConfigurationSince` requires the `IDENTIFIER` and string literal to be a `Version`, erroring otherwise, so we can have the flexibility to relax the syntax over time without it being a breaking change For example, if `--cfg=foo="1.0"` caused `cfg(since(foo, "1.0"))` to be `false` and we later allowed `"1.0"` for the `IDENTIFIER`, it would now be `true` and would change behavior. @@ -385,19 +400,19 @@ Like with Cargo, the `+build` metadata field should probably not be supported in If we were stricter on the syntax, we could allow for version numbers to be directly accepted, without quotes (e.g. `#[cfg(since(rust, 1.95.0))]`). -If we ever decided to support operators (e.g.`#[cfg(since(rust, "=1.95.0"))]`, see `--check-cfg`), then we'd need to decide if those also go outside the string or then require a string, being inconsistent. -This may limit us if we decide to allow for alternative version formats like with [target_version](#cfg_target_version) as they may not have formats that map well to SemVer. +If we ever decided to support operators (e.g.`#[cfg(since(rust, "=1.95"))]`, see `--check-cfg`), then we'd need to decide if those also go outside the string or then require a string, being inconsistent. +This may limit us if we decide to allow for alternative version formats like with [target_version](#cfg_target_version) as they may not have formats that map well to `Version`. Worst case, we'd need to accept arbitrary bare words. This would also be inconsistent with other uses of `cfg`s *but* maybe that would just be the start to natively supporting more types in `cfg`, like integers which are of interest to embedded folks. -A user could do `--cfg=foo --cfg=foo="1.2.0" --cfg=foo"1.3.0"`, leading to `cfg` to be a set of: +A user could do `--cfg=foo --cfg=foo="1.2" --cfg=foo"1.3"`, leading to `cfg` to be a set of: - `("foo", None)` -- `("foo", "1.2.0")` -- `("foo", "1.3.0")` +- `("foo", "1.2")` +- `("foo", "1.3")` -meaning `cfg(all(foo, foo = "1.2.0", foo = "1.3.0"))` is `true`. +meaning `cfg(all(foo, foo = "1.2", foo = "1.3"))` is `true`. We take this into account by checking if any cfg with the name `foo` matches `since`. Alternatively, we could fail the match in this case but that prevents `--cfg rust` for checking if this feature is stable. @@ -406,17 +421,17 @@ Alternatively, we could fail the match in this case but that prevents `--cfg rus The `--check-cfg` predicate and the value for `rust` ensures users get warnings about - Invalid syntax -- Using this with versions from before its supported, e.g. `#[cfg(since(rust, "1.0.0")]` +- Using this with versions from before its supported, e.g. `#[cfg(since(rust, "1.0")]` -`--check-cfg` requires a SemVer version, rather than a version requirement, -in case we want the future possibility of relaxing SemVer versions +`--check-cfg` requires a `Version`, rather than a version requirement, +in case we want the future possibility of relaxing `Version` *and* we want to infer from the fields used in `--check-cfg` to specify the maximum number of fields accepted in comparisons. Like with the cfg's string literal, check-cfg's string literal does not support the `+build` metadata field as it has no affect on precedence. We could have the check-cfg `since` predicate only apply to the cfg `since` predicate, -causing `#[cfg(rust = "1.100.0")]` to warn. +causing `#[cfg(rust = "1.100")]` to warn. However, - the `since` predicates are a general feature intended to be used with other version numbers where exact matches may also be appropriate. - this would get in the way of approximating the vendor version by the language version for working around compiler bugs and snapshotting of compiler output. @@ -426,7 +441,7 @@ Alternatively, we could try to find a way to structure `--check-cfg` to allow th One way of doing this is by allowing the check-cfg `since` predicate outside of the `values` predicate, meaning it works with the cfg `since` predicate and not the `=` operator. Another way would be for the check-cfg `since` predicate to never work with `=` but to instead -allow operators inside of the cfg `since` predicate, e.g. `#[cfg(since(rust, "=1.95.0"))]`. +allow operators inside of the cfg `since` predicate, e.g. `#[cfg(since(rust, "=1.95"))]`. However, with the rename of the predicate from `version` to `since`, operators don't fit in as easily. If someone wanted to support equality checks, there wouldn't be a way to support a continuous range of `values()` but would instead have to manually specify each likely potential version. @@ -475,6 +490,20 @@ Alternatively, we could have the built-in `--check-cfg` for `rust` include `valu - The lint is an opportunity to tell people how to suppress it in old versions - However, this does "punish" people who need it but don't care about warnings on old versions +### Patch + +The patch field is left off as the Language is not generally affected by patch releases. +We can always add the patch field at a later point. +In the future possibilities for relaxing `Version`, +we allow for variable precision with a max precision determined by what is used in check-cfg. +If we switch check-cfg to use 3 fields, +all 2-3 field precision versions would work. + +If nighties are still considered incomplete at that point, +there is the question of how we determine what version to report nightlies as. +We could either track the latest patch release at the time of the nightly, assume `x.y.0`, or assume `x.y.99`. +The compiler may choose to offer an unstable flag to mark a nightly as "complete" to allow for testing of features with `since`. + ### Pre-release When translating `rustc --version` to a language version, we have several choices when it comes to pre-releases, including: @@ -532,7 +561,7 @@ We'd need to add the concept of types to cfg. We could make check-cfg load-bearing by relying on its type information or we could add coercion functions to cfg. -So given `--cfg=rust --cfg=rust=version("1.95.0")`, you could do `cfg(rust >= version("1.95"))`. +So given `--cfg=rust --cfg=rust=version("1.95")`, you could do `cfg(rust >= version("1.95"))`. With typing, `cfg_values!` (a future possibility) could evaluate to the given type. @@ -787,14 +816,14 @@ Haskell: - In the future the `--check-cfg` `since()` predicate could make the minimum-version field optional, matching all version numbers. -- Adding `#[cfg(before("1.95.0"))]` could resolve the unnatural grammar of `#[cfg(not(since("1.95.0")))]`. +- Adding `#[cfg(before("1.95"))]` could resolve the unnatural grammar of `#[cfg(not(since("1.95")))]`. - Deferring to keep this minimal and to get more real world input on the usefulness of this - - Another possible name is `#[cfg(until("1.95.0"))]` which reads well as `#[cfg(not(until("1.95.0")))]` + - Another possible name is `#[cfg(until("1.95"))]` which reads well as `#[cfg(not(until("1.95")))]` -## Relaxing SemVer +## Relaxing `Version` -Instead of requiring the `IDENTIFIER` in the cfg `since` predicate to be strictly SemVer `major.minor.patch`, -we could allow abbreviated forms like `major.minor` or even `major`. +Instead of requiring the `IDENTIFIER` in the cfg `since` predicate to be strictly `Version` `major.minor`, +we could allow abbreviated forms like `major` or more precision like `major.minor.patch`. This would make the predicate more inclusive for other cases, like `edition`. The syntax for a version could be: @@ -851,6 +880,10 @@ This was adopted from [SemVer](https://semver.org/) with the following changes: - Unlike `PrereleaseVersion`, missing fields is assumed to be `0`, rather than lower precedence - Alphanumerics are allowed in release version fields +Compared to the base `Version` syntax, this adds back from SemVer: +- pre-release versions +- build metadata + The version requirement (string literal) for cfg `since` and check-cfg `since` would be similarly updated except `BuildMetadata` would not be allowed. A user would see the `unexpected_cfgs` lint if their cfg `since` string literal had more precision (more `VersionField`s) than the check-cfg `since` predicate. @@ -867,6 +900,8 @@ We could always relax this incrementally, e.g. Rustc could indicate that it implements an incomplete version of the compiler by having an `-incomplete` pre-release field. This would be used for nightlies and there would be a question of whether beta should be incomplete or not. +This would build ion the above relaxing of `Version` for at least being able to specify pre-release versions. + This can be done later as its unstable. A guide-level explanation would be: @@ -894,7 +929,7 @@ In adding a `cfg` for the Edition, we could model it as either: - A single-field version Assuming the latter, -we could have the following definition, building on the above relaxing of SemVer for at least variable alternative precision: +we could have the following definition, building on the above relaxing of `Version` for at least variable alternative precision: `--cfg edition=""` @@ -908,10 +943,10 @@ we could have the following definition, building on the above relaxing of SemVer Instead of defining a new `#[cfg]` predicate, [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750) could reuse the `#[cfg(since)]` predicate. -Building on the above relaxing of Semver, we should meet the needs of most versioning systems. +Building on the above relaxing of `Version`, we should meet the needs of most versioning systems. The one known exception is "post releases" (e.g. [`1.2.0.post1`](https://packaging.python.org/en/latest/discussions/versioning/) -which, if we translated it to SemVer's syntax of `1.2.0-post1`, would be treated as a pre-release. +which, if we translated it to `Version`'s syntax of `1.2.0-post1`, would be treated as a pre-release. We can translate this to extra precision, e.g. `1.2.0-post1` could be `1.2.0.post.1`. This would require the check-cfg `since` to use the appropriate amount of precision to not warn. @@ -939,8 +974,8 @@ this will allow users to check for specific nightly versions. Some challenges for this with `rustc --version`: - Nightly versions for a given release are mutable, all mapping to the `-nightly` pre-release version rather than including the date within the pre-release -- This does not conform to SemVer's precedence rules, - as `-nightly` is an older version than `-beta.4` while [SemVer's precedence rules](https://semver.org/#spec-item-11) say the opposite +- This does not conform to `Version`'s relaxed precedence rules, + as `-nightly` is an older version than `-beta.4` while `Version`s relaxed precedence rules say the opposite - Crater runs and local builds don't necessarily have a version that fits within this picture ## `#[cfg(nightly)]` @@ -969,19 +1004,19 @@ Open questions: - How does `cfg_value!(foo)` deal being unset? - Compiler error, like `env!`. Could provide an `option_cfg_value!`. - How does `cfg_value!(foo)` deal with name-only cfgs?? - - Ignoring them would work best for the purpose of `--cfg=rust --cfg=rust="1.95.0"` + - Ignoring them would work best for the purpose of `--cfg=rust --cfg=rust="1.95"` - How does `cfg_value!(foo)` deal with multiple cfg vales? - Compiler error ## `check-cfg` support for a version without a minimum -`--check-cfg 'cfg(foo, values(since("1.95.0")))'` requires setting a minimum version. +`--check-cfg 'cfg(foo, values(since("1.95")))'` requires setting a minimum version. If a user did not need that when setting a `cfg`, they would have to do `--check-cfg 'cfg(foo, values(since("0.0.0-0")))'`. A user may want a shorthand for this. With the name `since`, defaulting it to `"0.0.0-0"` doesn't read too well (--check-cfg 'cfg(foo, values(since()))'`). Maybe a new predicate can be added `version()`. -A shorthand may be limited to SemVer versions if we use the `since(version)` syntax to specify the supported version syntax, see [`--check-cfg` rationale][#--check-cfg-rationale]. +A shorthand may be limited to `Version` versions if we use the `since(version)` syntax to specify the supported version syntax, see [`--check-cfg` rationale][#--check-cfg-rationale]. ## An `is_set` predicate From b4338cb8ce5a84d15c0afb8ba33af1cc8205af23 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 15:20:11 -0500 Subject: [PATCH 105/109] feat(alt): Problem with future possibilities and operators --- text/3857-cfg-version.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index dc4d907adcf..b29735b8b5a 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -648,6 +648,9 @@ If we nest `since` inside `version`, - If this is just to make the name `since` more specific, we could just as well be served by naming it `version_since` +If we want to consider typed-config in the future (and by extension `cfg(rust >= "1.95")`), +we may want to hold off on the use of operators to give ourselves more flexibility in defining how they work. + ### `cfg(rust_version(1.95))` *(this is [RFC 2523](https://rust-lang.github.io/rfcs/2523-cfg-path-version.html))* From 6703ecd275cb55c73f30581b36bcacefa546299a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Sep 2025 15:32:55 -0500 Subject: [PATCH 106/109] fix(ref): Be more precise in the name with version_since --- text/3857-cfg-version.md | 218 ++++++++++++++++++++------------------- 1 file changed, 111 insertions(+), 107 deletions(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index b29735b8b5a..a49b1c0f8b3 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -8,19 +8,19 @@ Allow Rust-version conditional compilation by adding - a built-in `--cfg=rust --cfg=rust=""`, for the Rust language version -- `#[cfg(since(cfg_name, ""))]`, a minimum-version `cfg` predicate +- `#[cfg(version_since(cfg_name, ""))]`, a minimum-version `cfg` predicate Say this was added before 1.70, you could do: ```toml -[target.'cfg(not(since(rust, "1.70")))'.dependencies"] +[target.'cfg(not(version_since(rust, "1.70")))'.dependencies"] is-terminal = "0.4.16" ``` ```rust fn is_stderr_terminal() -> bool { - #[cfg(since(rust, "1.70"))] + #[cfg(version_since(rust, "1.70"))] use std::io::IsTerminal as _; - #[cfg(not(since(rust, "1.70")))] + #[cfg(not(version_since(rust, "1.70")))] use is_terminal::IsTerminal as _; std::io::stderr().is_terminal() @@ -126,12 +126,12 @@ This can be accomplished by conditionally compiling the code for that feature. As its hard to talk about features and versions in the future, we're going to step through this in an alternate reality where: - `--check-cfg` (warn on invalid conditional compilation) was stabilized in 1.0 -- `--cfg rust` and `#[cfg(since)]` were stabilized in 1.20 +- `--cfg rust` and `#[cfg(version_since)]` were stabilized in 1.20 - `#[must_use]` (an example language feature) was still stabilized in 1.27 For instance, say you have an MSRV of 1.20, to use `#[must_use]` you would do: ```rust -#[cfg_attr(since(rust, "1.27"), must_use)] +#[cfg_attr(version_since(rust, "1.27"), must_use)] fn double(x: i32) -> i32 { 2 * x } @@ -146,7 +146,7 @@ fn main() { > Side note: if we also had [RFC 3804](https://github.com/rust-lang/rfcs/pull/3804), > we can give this condition a semantic name and avoid duplicating it, reducing the chance of bugs: > ```rust -> #[cfg_alias(must_use_exists, since(rust, "1.27"))] +> #[cfg_alias(must_use_exists, version_since(rust, "1.27"))] > > #[cfg_attr(must_use_exists, must_use)] > fn double(x: i32) -> i32 { @@ -162,11 +162,11 @@ fn main() { Now, let's say your MSRV is 1.10, -the above code would error when compiling with your MSRV because the `since` predicate does not exist with that version. +the above code would error when compiling with your MSRV because the `version_since` predicate does not exist with that version. However, the presence of `--cfg rust` implies that we are on 1.27, -so you can "detect" support for `since` by changing your code to: +so you can "detect" support for `version_since` by changing your code to: ```rust -#[cfg_attr(rust, cfg_attr(since(rust, "1.27"), must_use))] +#[cfg_attr(rust, cfg_attr(version_since(rust, "1.27"), must_use))] fn double(x: i32) -> i32 { 2 * x } @@ -186,14 +186,14 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust)'] } # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -## `since` cfg predicate +## `version_since` cfg predicate -A `since` cfg predicate will be added to Rust. +A `version_since` cfg predicate will be added to Rust. As Cargo mirrors Rust's `#[cfg]` syntax, it too will gain this predicate. The [syntax](https://doc.rust-lang.org/reference/conditional-compilation.html#grammar-ConfigurationPredicate) is: ``` -ConfigurationSince -> `since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` +ConfigurationSince -> `version_since` `(` IDENTIFIER `,` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` Version = NumericVersionField `.` NumericVersionField @@ -202,14 +202,14 @@ NumericVersionField -> | ( [`1`..`9`] DEC_DIGIT* ) ``` -When evaluating `since`, +When evaluating `version_since`, 1. If the string literal does not conform to the syntax `Version`, the compiler will error. 2. If `IDENTIFIER` is unset, this will evaluate to `false`. -3. If any of the following evaluates to `true` for any cfg entry for `IDENTIFIER`, `since` will evaluate to `true`, otherwise `false`. +3. If any of the following evaluates to `true` for any cfg entry for `IDENTIFIER`, `version_since` will evaluate to `true`, otherwise `false`. 1. If `IDENTIFIER` is name-only, this entry will evaluate to `false`. 2. If `IDENTIFIER`'s value is not a valid `Version`, the compiler will error. 3. Otherwise, if `IDENTIFIER`s value has the same or higher precedence, this entry will evaluate to `true` - For example, `#[cfg(since(rust, "1.90"))]` will be interpreted as `precedence_of(1.95.2) >= precedence_of(1.90.0)`. + For example, `#[cfg(version_since(rust, "1.90"))]` will be interpreted as `precedence_of(1.95.2) >= precedence_of(1.90.0)`. With the precedence of: - Precedence is calculated by separating the `Version` into the respective `NumericVersionField`s @@ -222,13 +222,13 @@ This was adopted from [SemVer](https://semver.org/) with the following changes: - Removed build metadata (which does not affect precedence) Examples: -- `cfg(since(unset_name, "1.0"))` will be false -- `--cfg name_only` and `cfg(since(name_only, "1.0"))` will be false -- `--cfg foo="bird"` and `cfg(since(name_only, "1.0"))` will be a compiler error -- `--cfg foo="1.1"` and `cfg(since(foo, "bird"))` will be a compiler error -- `--cfg foo="1.1"` and `cfg(since(foo, "1.0"))` will be true -- `--cfg foo="1.1"` and `cfg(since(foo, "1.2"))` will be false -- `--cfg foo --cfg foo="1.1" --cfg foo="1.0"` and `cfg(since(foo, "1.1"))` will be true +- `cfg(version_since(unset_name, "1.0"))` will be false +- `--cfg name_only` and `cfg(version_since(name_only, "1.0"))` will be false +- `--cfg foo="bird"` and `cfg(version_since(name_only, "1.0"))` will be a compiler error +- `--cfg foo="1.1"` and `cfg(version_since(foo, "bird"))` will be a compiler error +- `--cfg foo="1.1"` and `cfg(version_since(foo, "1.0"))` will be true +- `--cfg foo="1.1"` and `cfg(version_since(foo, "1.2"))` will be false +- `--cfg foo --cfg foo="1.1" --cfg foo="1.0"` and `cfg(version_since(foo, "1.1"))` will be true The compiler implementation currently treats cfgs as `HashSet<(String, Option)>` and would likely need to change this to `HashMap>>`` @@ -238,7 +238,7 @@ to accommodate this predicate. A new predicate will be added of the form: ``` -CheckConfigurationSince -> `since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` +CheckConfigurationSince -> `version_since` `(` ( STRING_LITERAL | RAW_STRING_LITERAL ) `)` ``` The syntax for the contents of the string literal is a `Version`. @@ -247,23 +247,23 @@ This will specify that for the given cfg, string literals will be valid if: - `Version` syntax - from the specified version and up -When checking a `since` predicate, +When checking a `version_since` predicate, - the string literal must be a minimum version requirement that specifies a subset of what the `--check-cfg` specifies *note: non-version string literals are already a compiler error* This composes with all other values specified with the `values()` predicate -So given `--check-cfg 'cfg(foo, values(since("1.95")))'`, +So given `--check-cfg 'cfg(foo, values(version_since("1.95")))'`, - ✅ `#[cfg(foo = "1.100")]` - ⚠️ `#[cfg(foo = "1.100.0")]`: not `Version` syntax -- ✅ `#[cfg(since(foo, "1.95"))]` -- ✅ `#[cfg(since(foo, "1.100"))]` -- ✅ `#[cfg(since(foo, "3.0"))]` -- ✅ `#[cfg(since(foo, "1.95"))]` -- ⚠️ `#[cfg(since(foo, "1.90"))]`: matches a superset of `--check-cfg` -- ⚠️ `#[cfg(since(foo, "1"))]`: not `Version` syntax -- ⚠️ `#[cfg(since(foo, "bar"))]`: not `Version` syntax +- ✅ `#[cfg(version_since(foo, "1.95"))]` +- ✅ `#[cfg(version_since(foo, "1.100"))]` +- ✅ `#[cfg(version_since(foo, "3.0"))]` +- ✅ `#[cfg(version_since(foo, "1.95"))]` +- ⚠️ `#[cfg(version_since(foo, "1.90"))]`: matches a superset of `--check-cfg` +- ⚠️ `#[cfg(version_since(foo, "1"))]`: not `Version` syntax +- ⚠️ `#[cfg(version_since(foo, "bar"))]`: not `Version` syntax ## `rust` cfg @@ -273,7 +273,7 @@ This will be the version of `rustc` without the patch field and with the behavio We expect rustc to treat beta and nightly versions as an "incomplete" implementation of that language version, reporting some number less than the current nightly. -`rust` will be specified as `--check-cfg 'cfg(rust, values(since("1.95")))'` +`rust` will be specified as `--check-cfg 'cfg(rust, values(version_since("1.95")))'` (or whatever version this gets stabilized in). Like with `--check-cfg` for Cargo `features`, the compiler may choose to add additional context for why this lower bound is present (not stabilized). @@ -288,12 +288,12 @@ Cargo will expose `rust` in: ## clippy Clippy has a [`clippy::incompatible_msrv`](https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv) lint -which will fire whenever a standard library item is used with a `#[stable(since)]` newer than `package.rust-version`. -However, it will be perfectly reasonable to use those items when guarded by a `#[cfg(since)]`. +which will fire whenever a standard library item is used with a `#[stable(version_since)]` newer than `package.rust-version`. +However, it will be perfectly reasonable to use those items when guarded by a `#[cfg(version_since)]`. Clippy may wish to: -- Find a way to reduce false positives, e.g. evaluating the `cfg(since)`s that led to the item's usage or disabling the lint within `#[cfg(since)]` -- Suggest `#[cfg(since)]` in the `clippy::incompatible_msrv` diagnostic report (maybe along with offering to bump MSRV as that is a reasonable alternative) +- Find a way to reduce false positives, e.g. evaluating the `cfg(version_since)`s that led to the item's usage or disabling the lint within `#[cfg(version_since)]` +- Suggest `#[cfg(version_since)]` in the `clippy::incompatible_msrv` diagnostic report (maybe along with offering to bump MSRV as that is a reasonable alternative) # Drawbacks [drawbacks]: #drawbacks @@ -304,15 +304,15 @@ At least a preliminary search of GitHub did not uncover uses but that search may have been incomplete and that data set is biased towards open source and not all uses of Rust. -Ignoring the logic, a straight-English reading of `#[cfg(not(since("1.95")))]` is unnatural and could cause confusion. +Ignoring the logic, a straight-English reading of `#[cfg(not(version_since("1.95")))]` is unnatural and could cause confusion. This can be mitigated by use of `#[cfg_alias]` which will let users provide a semantic name for the positive case that works with the negative case, on top of the other benefits of providing a central, semantic name. -This could also be helped by supporting a `#[cfg(before("1.95"))]`. +This could also be helped by supporting a `#[cfg(version_before("1.95"))]`. This was left to [a future possibility][future-possibilities]. While Rust can stacks `cfg`s to test for the presence of this feature on older versions, -this does not include a solution for adopting this within `Cargo.toml` without waiting for an MSRV bump to the version `since` is stabilized in. +this does not include a solution for adopting this within `Cargo.toml` without waiting for an MSRV bump to the version `version_since` is stabilized in. Traditionally, maintainers only test their MSRV and latest stable, assuming those will catch every issue. While that isn't always true today (e.g. some Cargo features go from "unknown" warning to "unstable" error to supported and MSRV might be in the warning phase), @@ -333,16 +333,16 @@ as there are more corner cases to cover, particularly with how this integrates w ## Pre-releases for major versions -Pre-releases of major versions isn't a consideration for `rust` but in the general use of `since`. +Pre-releases of major versions isn't a consideration for `rust` but in the general use of `version_since`. If wanting to split a continuous range with minor and patch versions, -`#[cfg(since(foo, "1.1"))]` and `#[cfg(not(since(foo, "1.1")))]` +`#[cfg(version_since(foo, "1.1"))]` and `#[cfg(not(version_since(foo, "1.1")))]` works reasonably well. The problem comes into play when doing so with major versions when pre-releases are involved, -like `#[cfg(since(foo, "2.0"))]` and `#[cfg(not(since(foo, "2.0")))]`. +like `#[cfg(version_since(foo, "2.0"))]` and `#[cfg(not(version_since(foo, "2.0")))]`. In this situation, a `2.0.0-dev.5` will match the second condition when the user likely only wanted to include `1.*`. -Instead, they should do `#[cfg(since(foo, "2.0.0-0"))]` and `#[cfg(not(since(foo, "2.0.0-0")))]` or have a third case for pre-releases of `foo@2.0.0`. +Instead, they should do `#[cfg(version_since(foo, "2.0.0-0"))]` and `#[cfg(not(version_since(foo, "2.0.0-0")))]` or have a third case for pre-releases of `foo@2.0.0`. This came up in Cargo when considering how to improve interactions with pre-releases. Cargo has the advantages of: @@ -355,42 +355,44 @@ see [cargo#14305](https://github.com/rust-lang/cargo/pull/14305). # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -## `since` cfg predicate rationale +## `version_since` cfg predicate rationale -The `since` name was taken from -[rustversion](https://crates.io/crates/rustversion) and the `#[deprecated(since)]` / `#[stable(since)]` attributes. -This better conveys what operation is being performed than the original `version` name -and leaves room for related predicates like `before`. +The `version_since` name was taken from +[`rustversion::since`](https://crates.io/crates/rustversion) and the `#[deprecated(since)]` / `#[stable(version_since)]` attributes. +We need to express both what comparison is happening (`>=`) and the comparison algorithm (version sort). +This also leaves room for related predicates like `version_before`. In particular, as this is a general feature and not just for Rust version comparisons, -we need to consider cases like `version(python, "2.8")` and whether people would interpret that as an exact match, a SemVer compatible match (`^` in cargo), or a `>=` match (the winner). -We could also call this `minimum`, or support comparison operators in the spirit of [RFC 3796](https://github.com/rust-lang/rfcs/pull/3796). -The risk with a general word like `since` is if we gain support for other data types in cfgs, like integers for embedded development. -The name `since` might apply in some situations but not others and its unclear if we'd want to generalize it past versions. +we need to consider cases with the original `version` name like `version(python, "2.8")` and whether people would interpret that as an exact match, a SemVer compatible match (`^` in cargo), or a `>=` match (the winner). +We could also call this `version_minimum` or `version_ge`, or support comparison operators in the spirit of [RFC 3796](https://github.com/rust-lang/rfcs/pull/3796). +Including `since` reduces the risk if we gain support for other data types in cfgs, like integers for embedded development. +Naming it only `since` might apply in some situations but not others and its unclear if we'd want to generalize it past versions. Having a more specific name like `version_since` / `since_version` could avoid these concerns. +We chose `version_since` since it can read as "for versions since rust 1.20" and follows the type-method naming style. +However, the name `version_since` can seem excessively verbose, especially compared to `version` or `since`. -We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(since("1.95"))]` as a shorthand. +We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(version_since("1.95"))]` as a shorthand. However, this would look confusing in Cargo and doesn't seem like its offering enough of a benefit to be worth the costs. `ConfigurationSince` requires the `IDENTIFIER` and string literal to be a `Version`, erroring otherwise, so we can have the flexibility to relax the syntax over time without it being a breaking change -For example, if `--cfg=foo="1.0"` caused `cfg(since(foo, "1.0"))` to be `false` and we later allowed `"1.0"` for the `IDENTIFIER`, it would now be `true` and would change behavior. -Because we'll have `since(rust, _)` at that point, it won't require an MSRV bump. +For example, if `--cfg=foo="1.0"` caused `cfg(version_since(foo, "1.0"))` to be `false` and we later allowed `"1.0"` for the `IDENTIFIER`, it would now be `true` and would change behavior. +Because we'll have `version_since(rust, _)` at that point, it won't require an MSRV bump. This does leave the door open for us to relax this in the future once we become comfortable with the flexibility of our version syntax. Alternatively, we could try to determine a flexible-enough version syntax now though that comes with the risk that it isn't sufficient. -Another benefit to erroring is so `not(since(invalid, ""))` is not `true`. +Another benefit to erroring is so `not(version_since(invalid, ""))` is not `true`. Having a unset or name-only `IDENTIFIER` evaluate to `false` is consistent with `cfg(IDENTIFIER)` and `cfg(IDENTIFIER = "value")`. When a version can be conditionally present, it avoids the need to gate an expression which would either require including `--cfg IDENTIFIER` with `--cfg IDENTIFIER=""` (like `--cfg rust`) to check for its presence or for us to add an `is_set` predicate. -However, this would also apply to a `before` predicate, making `before` not the same as `not(since)`. +However, this would also apply to a `version_before` predicate, making `version_before` not the same as `not(version_since)`. If we did error on unset or name-only `IDENTIFIER`s, we'd need it to be done lazily so as to not error if the expression is gated. Deferring the more flexible syntax avoids having to couple this decision to what syntax should be allowed which will allow us to better evaluate the ramifications for each time we relax things. For instance, in the [future-possibilities] we go so far as to allow alphabetic characters in any field while making the precision arbitrary. -This can have side effects like allowing comparing words like with `#[cfg(since(hello, "world"))]`, +This can have side effects like allowing comparing words like with `#[cfg(version_since(hello, "world"))]`, whether intended by the users (potential abuse of the feature) or not (masking errors that could help find bugs). Deferring `+build` metadata field support for `IDENTIFIER`s value because a non-precedence setting field can cause confusion (as shown in Cargo/crates.io), @@ -399,8 +401,8 @@ Like with Cargo, the `+build` metadata field should probably not be supported in If we were stricter on the syntax, we could allow for version numbers to be directly accepted, without quotes -(e.g. `#[cfg(since(rust, 1.95.0))]`). -If we ever decided to support operators (e.g.`#[cfg(since(rust, "=1.95"))]`, see `--check-cfg`), then we'd need to decide if those also go outside the string or then require a string, being inconsistent. +(e.g. `#[cfg(version_since(rust, 1.95.0))]`). +If we ever decided to support operators (e.g.`#[cfg(version_since(rust, "=1.95"))]`, see `--check-cfg`), then we'd need to decide if those also go outside the string or then require a string, being inconsistent. This may limit us if we decide to allow for alternative version formats like with [target_version](#cfg_target_version) as they may not have formats that map well to `Version`. Worst case, we'd need to accept arbitrary bare words. This would also be inconsistent with other uses of `cfg`s @@ -414,14 +416,14 @@ A user could do `--cfg=foo --cfg=foo="1.2" --cfg=foo"1.3"`, leading to `cfg` to meaning `cfg(all(foo, foo = "1.2", foo = "1.3"))` is `true`. -We take this into account by checking if any cfg with the name `foo` matches `since`. +We take this into account by checking if any cfg with the name `foo` matches `version_since`. Alternatively, we could fail the match in this case but that prevents `--cfg rust` for checking if this feature is stable. ## `--check-cfg` rationale The `--check-cfg` predicate and the value for `rust` ensures users get warnings about - Invalid syntax -- Using this with versions from before its supported, e.g. `#[cfg(since(rust, "1.0")]` +- Using this with versions from before its supported, e.g. `#[cfg(version_since(rust, "1.0")]` `--check-cfg` requires a `Version`, rather than a version requirement, in case we want the future possibility of relaxing `Version` @@ -430,19 +432,19 @@ in case we want the future possibility of relaxing `Version` Like with the cfg's string literal, check-cfg's string literal does not support the `+build` metadata field as it has no affect on precedence. -We could have the check-cfg `since` predicate only apply to the cfg `since` predicate, +We could have the check-cfg `version_since` predicate only apply to the cfg `version_since` predicate, causing `#[cfg(rust = "1.100")]` to warn. However, -- the `since` predicates are a general feature intended to be used with other version numbers where exact matches may also be appropriate. +- the `version_since` predicates are a general feature intended to be used with other version numbers where exact matches may also be appropriate. - this would get in the way of approximating the vendor version by the language version for working around compiler bugs and snapshotting of compiler output. Possibly there could be a clippy lint specifically about `rust = ""`. Alternatively, we could try to find a way to structure `--check-cfg` to allow the person setting the `check-cfg` to decide whether it can be used with `=` or not. -One way of doing this is by allowing the check-cfg `since` predicate outside of the `values` predicate, -meaning it works with the cfg `since` predicate and not the `=` operator. -Another way would be for the check-cfg `since` predicate to never work with `=` but to instead -allow operators inside of the cfg `since` predicate, e.g. `#[cfg(since(rust, "=1.95"))]`. -However, with the rename of the predicate from `version` to `since`, operators don't fit in as easily. +One way of doing this is by allowing the check-cfg `version_since` predicate outside of the `values` predicate, +meaning it works with the cfg `version_since` predicate and not the `=` operator. +Another way would be for the check-cfg `version_since` predicate to never work with `=` but to instead +allow operators inside of the cfg `version_since` predicate, e.g. `#[cfg(version_since(rust, "=1.95"))]`. +However, with the rename of the predicate from `version` to `version_since`, operators don't fit in as easily. If someone wanted to support equality checks, there wouldn't be a way to support a continuous range of `values()` but would instead have to manually specify each likely potential version. ## `rust` cfg rationale @@ -460,16 +462,16 @@ and adding the qualifier now may improve consistency with the future. `--cfg=rust` is added to allow `#[cfg(rust)]` checks so packages can immediately adopt this feature without bumping an MSRV. This does lock us into how a `cfg_value!(rust)` would work from the [future-possibilities]. -Alternatively, we could add a separate cfg, like `has_rust`, `rust_is_set`, `has_since`. +Alternatively, we could add a separate cfg, like `has_rust`, `rust_is_set`, `has_version_since`. `--check-cfg` will cause the following to warn: ```rust fn is_stderr_terminal() -> bool { #[cfg(rust)] - #[cfg(since(rust, "1.70"))] + #[cfg(version_since(rust, "1.70"))] use std::io::IsTerminal as _; #[cfg(rust)] - #[cfg(not(since(rust, "1.70")))] + #[cfg(not(version_since(rust, "1.70")))] use is_terminal::IsTerminal as _; #[cfg(not(rust))] use is_terminal::IsTerminal as _; @@ -502,7 +504,7 @@ all 2-3 field precision versions would work. If nighties are still considered incomplete at that point, there is the question of how we determine what version to report nightlies as. We could either track the latest patch release at the time of the nightly, assume `x.y.0`, or assume `x.y.99`. -The compiler may choose to offer an unstable flag to mark a nightly as "complete" to allow for testing of features with `since`. +The compiler may choose to offer an unstable flag to mark a nightly as "complete" to allow for testing of features with `version_since`. ### Pre-release @@ -518,13 +520,13 @@ The initial implementation treated nightlies as complete. This was [changed to incomplete](https://github.com/rust-lang/rust/pull/72001) after [some discussion](https://github.com/rust-lang/rust/issues/64796#issuecomment-624673206). In particular, this is important for -- the case of package `bleeding-edge` starting to use a new feature behind `#[cfg(since)]` and package `nightly-only` has their toolchain pinned to a nightly before the feature was stabilized (to ensure consistent behavior of unstable features), package `nightly-only` cannot add or update their dependency on `bleeding-edge` without getting a "feature gate needed" error. +- the case of package `bleeding-edge` starting to use a new feature behind `#[cfg(version_since)]` and package `nightly-only` has their toolchain pinned to a nightly before the feature was stabilized (to ensure consistent behavior of unstable features), package `nightly-only` cannot add or update their dependency on `bleeding-edge` without getting a "feature gate needed" error. - bisecting nightlies. This was [changed back to complete](https://github.com/rust-lang/rust/pull/81468) after [some more discussion](https://github.com/rust-lang/rust/issues/64796#issuecomment-634546711). In particular, this is important for -- keeping friction down for packages preparing for stabilized-on-nightly features as their `#[cfg(since)]`s can be inserted and "just work" which can be important for getting feedback quickly while the feature is easier to adapt to feedback that can be gained from these users +- keeping friction down for packages preparing for stabilized-on-nightly features as their `#[cfg(version_since)]`s can be inserted and "just work" which can be important for getting feedback quickly while the feature is easier to adapt to feedback that can be gained from these users - releasing the package while its in this state puts it at risk to be broken if the feature is changed after stabilization For RFC 2523, they settled on pre-releases being incomplete, @@ -532,7 +534,7 @@ favoring maintainers to adopt stabilized-on-nightly features immediately while letting people on pinned nightlies or bisecting nightlies to set a `-Z` to mark the version as incomplete. Originally, this RFC chose to translate `-nightly` to `-incomplete` because: -- Maintainers can adopt stabilized-on-nightly features with `#[cfg(since(rust, "1.100.0-0"))]` (the lowest pre-release for `1.100.0`), keeping friction low while explicitly acknowledging that the unstable feature may change +- Maintainers can adopt stabilized-on-nightly features with `#[cfg(version_since(rust, "1.100.0-0"))]` (the lowest pre-release for `1.100.0`), keeping friction low while explicitly acknowledging that the unstable feature may change - `-0` is recommended over `-incomplete` or any other value as the exact pre-release value is unspecified. - Allows build scripts to experiment with other logic when approximating the vendor version from the language version with less of a chance of needing to invoke `rustc` (e.g. detecting nightly) - It provides extra context when approximating the vendor version from the language version when populating build information @@ -624,7 +626,7 @@ though this may be relaxed, see [cargo#14305](https://github.com/rust-lang/cargo This does not fit with out use cases because it causes discontinuities while users of the `cfg` need continuity. -This allows moving to a more specialized outer predicate name than `since` without losing the conveyed meaning. +This splits our specific predicate name (`version_since`) into smaller parts without losing conveyed meaning. If the operator is outside of the string literal - we could also make it a bare word but that could lead to problems when dealing with relaxing of the version syntax @@ -637,16 +639,16 @@ If the operator is inside the string literal - users may stumble and be frustrated with missing features from cargo (do we include all unary and binary operators?) - behavior differences with Cargo may be needed due to different use cases but could lead to user bugs and frustration as it might not match what users are familiar with -If we nest `since` inside `version`, -- If there is a concern with boundary with `since` conditions that aren't alleviated by the discussion elsewhere, - then this isn't helped because we are still using `since` +If we nest `version_since` inside `version`, +- If there is a concern with boundary with `version_since` conditions that aren't alleviated by the discussion elsewhere, + then this isn't helped because we are still using `version_since` - It's not clear how a user is expected to reason about this (i.e. how do we teach this?) especially in light of how the existing predicates work - This creates a DSL inside our existing DSL which feels tacked on like using [rustversion](https://crates.io/crates/rustversion) - Users are likely to hit impedance mismatches between principles they expect to work within the parent DSL and this DSL (e.g. using `all`) - Nesting APIs puts more of a burden on the user, their editing experience, and our documentation structure to navigate compared to a flat structure - - If this is just to make the name `since` more specific, - we could just as well be served by naming it `version_since` + - If this is just to make the name `version_since` more specific, + we could just as well be served by naming it `version_version_since` If we want to consider typed-config in the future (and by extension `cfg(rust >= "1.95")`), we may want to hold off on the use of operators to give ourselves more flexibility in defining how they work. @@ -668,8 +670,8 @@ there was concern about the name "rust" in this predicate not fitting in with th However, dropping it to `version` would make things awkward in Cargo where there wouldn't be enough context for which item's `version` is being referred to. There is also a future possibility of better integrating dependency versions into the language. If done, then `version` may become more ambiguous even in Rust. -For example, if Cargo told rustc the minimum compatible version for a dependency, `#[deprecated(since)]`` warnings could not emit if the minimum version bound is lower than `since`. -Similarly, if we stabilized `#[stable(since)]`, a linter could report when a version requirement is too low. +For example, if Cargo told rustc the minimum compatible version for a dependency, `#[deprecated(version_since)]`` warnings could not emit if the minimum version bound is lower than `version_since`. +Similarly, if we stabilized `#[stable(version_since)]`, a linter could report when a version requirement is too low. We could rename this to `version` and stabilize it as-is, with this RFC being a future possibility that adds an optional second parameter for specifying which version is being referred to. @@ -809,7 +811,7 @@ Haskell: - `--cfg rust` or `--cfg has_rust` for using now without an MSRV bump? - Should the `check-cfg` include `values(none())` or not? - How strict should the version syntax be at this stage? -- `since(rust, "1.95")`, `version_since(rust, "1.95")`, `version(rust, ">=1.95")`, `version(rust >= "1.95")`, or `version(rust, since("1.95"))` +- `version_since(rust, "1.95")`, `version_version_since(rust, "1.95")`, `version(rust, ">=1.95")`, `version(rust >= "1.95")`, or `version(rust, version_since("1.95"))` - Is `"1.95.0-incomplete"` an acceptable compromise on the question of whether to treat nightlies as complete or incomplete? - How much do we care about the name? - Are beta's incomplete? Strictly speaking, yes. However, in most cases they will be complete. @@ -817,15 +819,15 @@ Haskell: # Future possibilities [future-possibilities]: #future-possibilities -- In the future the `--check-cfg` `since()` predicate could make the minimum-version field optional, +- In the future the `--check-cfg` `version_since()` predicate could make the minimum-version field optional, matching all version numbers. -- Adding `#[cfg(before("1.95"))]` could resolve the unnatural grammar of `#[cfg(not(since("1.95")))]`. +- Adding `#[cfg(version_before("1.95"))]` could resolve the unnatural grammar of `#[cfg(not(version_since("1.95")))]`. - Deferring to keep this minimal and to get more real world input on the usefulness of this - - Another possible name is `#[cfg(until("1.95"))]` which reads well as `#[cfg(not(until("1.95")))]` + - Another possible name is `#[cfg(version_until("1.95"))]` which reads well as `#[cfg(not(version_until("1.95")))]` ## Relaxing `Version` -Instead of requiring the `IDENTIFIER` in the cfg `since` predicate to be strictly `Version` `major.minor`, +Instead of requiring the `IDENTIFIER` in the cfg `version_since` predicate to be strictly `Version` `major.minor`, we could allow abbreviated forms like `major` or more precision like `major.minor.patch`. This would make the predicate more inclusive for other cases, like `edition`. @@ -887,9 +889,9 @@ Compared to the base `Version` syntax, this adds back from SemVer: - pre-release versions - build metadata -The version requirement (string literal) for cfg `since` and check-cfg `since` would be similarly updated +The version requirement (string literal) for cfg `version_since` and check-cfg `version_since` would be similarly updated except `BuildMetadata` would not be allowed. -A user would see the `unexpected_cfgs` lint if their cfg `since` string literal had more precision (more `VersionField`s) than the check-cfg `since` predicate. +A user would see the `unexpected_cfgs` lint if their cfg `version_since` string literal had more precision (more `VersionField`s) than the check-cfg `version_since` predicate. Note: for `--cfg foo="bar"`, `"bar"` would be a valid version. @@ -913,7 +915,7 @@ A guide-level explanation would be: > for when it hits stable, > you would instead use `"1.27.0-0"` to match all pre-release versions of 1.27.0: > ```rust -> #[cfg_attr(since(rust, "1.27.0-0"), must_use)] +> #[cfg_attr(version_since(rust, "1.27.0-0"), must_use)] > fn double(x: i32) -> i32 { > 2 * x > } @@ -936,32 +938,32 @@ we could have the following definition, building on the above relaxing of `Versi `--cfg edition=""` -`--check-cfg cfg(edition, values(2015, 2018, 2021, 2024, since(2025)))` +`--check-cfg cfg(edition, values(2015, 2018, 2021, 2024, version_since(2025)))` - The discrete values for known editions is there to help catch mistakes -- `since(2025)` is used so packages don't have to deal with `unexpected_cfgs` when operating with edition versions higher than their current compiler recognizes and without having to try to predict what our future edition versions and policies may be -- `since(2025)` also ensures that a user gets an `unexpected_cfgs` warning if they do `cfg(since(edition, 2028.10))` as that matches the `since(2025)` but has more precision +- `version_since(2025)` is used so packages don't have to deal with `unexpected_cfgs` when operating with edition versions higher than their current compiler recognizes and without having to try to predict what our future edition versions and policies may be +- `version_since(2025)` also ensures that a user gets an `unexpected_cfgs` warning if they do `cfg(version_since(edition, 2028.10))` as that matches the `version_since(2025)` but has more precision ## `cfg_target_version` Instead of defining a new `#[cfg]` predicate, [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750) -could reuse the `#[cfg(since)]` predicate. +could reuse the `#[cfg(version_since)]` predicate. Building on the above relaxing of `Version`, we should meet the needs of most versioning systems. The one known exception is "post releases" (e.g. [`1.2.0.post1`](https://packaging.python.org/en/latest/discussions/versioning/) which, if we translated it to `Version`'s syntax of `1.2.0-post1`, would be treated as a pre-release. We can translate this to extra precision, e.g. `1.2.0-post1` could be `1.2.0.post.1`. -This would require the check-cfg `since` to use the appropriate amount of precision to not warn. +This would require the check-cfg `version_since` to use the appropriate amount of precision to not warn. If this is still not sufficient, we some options include: -- Add an optional third field for specifying the version format (e.g. `#[cfg(since(windows, "10.0.10240", )]`) +- Add an optional third field for specifying the version format (e.g. `#[cfg(version_since(windows, "10.0.10240", )]`) - Make `--check-cfg` load-bearing by having the version policy name be specified in the `--check-cfg` predicate ## Conditional compilation for dependency versions As the ecosystem grows and matures, the Rust language and standard library may not be the only dependencies users wish to support multiple versions of. -We may want to allow `#(cfg(since(serde, "1.0.900")]`. +We may want to allow `#(cfg(version_since(serde, "1.0.900")]`. As dependency versions can have a `+build` metadata field, we'd need to decide whether to further relax version numbers by allowing a `+build` metadata field @@ -1013,13 +1015,13 @@ Open questions: ## `check-cfg` support for a version without a minimum -`--check-cfg 'cfg(foo, values(since("1.95")))'` requires setting a minimum version. +`--check-cfg 'cfg(foo, values(version_since("1.95")))'` requires setting a minimum version. If a user did not need that when setting a `cfg`, -they would have to do `--check-cfg 'cfg(foo, values(since("0.0.0-0")))'`. +they would have to do `--check-cfg 'cfg(foo, values(version_since("0.0.0-0")))'`. A user may want a shorthand for this. -With the name `since`, defaulting it to `"0.0.0-0"` doesn't read too well (--check-cfg 'cfg(foo, values(since()))'`). +With the name `version_since`, defaulting it to `"0.0.0-0"` doesn't read too well (--check-cfg 'cfg(foo, values(version_since()))'`). Maybe a new predicate can be added `version()`. -A shorthand may be limited to `Version` versions if we use the `since(version)` syntax to specify the supported version syntax, see [`--check-cfg` rationale][#--check-cfg-rationale]. +A shorthand may be limited to `Version` versions if we use the `version_since(version)` syntax to specify the supported version syntax, see [`--check-cfg` rationale][#--check-cfg-rationale]. ## An `is_set` predicate @@ -1028,9 +1030,11 @@ which would work like a `cfg` version of [`cfg_accessible`](https://dev-doc.rust-lang.org/stable/unstable-book/library-features/cfg-accessible.html) so long as the `cfg` is unconditionally set. -## Clippy lint: prefer using `cfg(since)` via `cfg_alias!` +## Clippy lint: prefer using `cfg(version_since)` via `cfg_alias!` Once we have this RFC and [RFC 3804](https://github.com/rust-lang/rfcs/pull/3804), -we may want a restriction lint that would encourage people to use `cfg(since)` through `cfg_alias` +we may want a restriction lint that would encourage people to use `cfg(version_since)` through `cfg_alias` so people work off of names in `cfg()` throughout their code base, rather than versions that may be duplicated and lack semantic meaning. + +This will also make the more awkward name of `version_since` less of an issue. From ff3f0759daccac29fe7b135946fda186a6f4c898 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 25 Sep 2025 14:22:20 -0500 Subject: [PATCH 107/109] feat(alt): Provide another reason for version(since) --- text/3857-cfg-version.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index a49b1c0f8b3..5bb6282dd40 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -627,6 +627,8 @@ This does not fit with out use cases because it causes discontinuities while users of the `cfg` need continuity. This splits our specific predicate name (`version_since`) into smaller parts without losing conveyed meaning. +This also provides a natural home for common documentation for cfg version concepts, +if we gain more predicates. If the operator is outside of the string literal - we could also make it a bare word but that could lead to problems when dealing with relaxing of the version syntax From ce9390b96789781dd08c4e8aaba33100b7f43505 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 25 Sep 2025 14:23:30 -0500 Subject: [PATCH 108/109] feat(rationale): More reasons for version_since than since_version --- text/3857-cfg-version.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 5bb6282dd40..9ebc1d6c248 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -367,7 +367,7 @@ We could also call this `version_minimum` or `version_ge`, or support comparison Including `since` reduces the risk if we gain support for other data types in cfgs, like integers for embedded development. Naming it only `since` might apply in some situations but not others and its unclear if we'd want to generalize it past versions. Having a more specific name like `version_since` / `since_version` could avoid these concerns. -We chose `version_since` since it can read as "for versions since rust 1.20" and follows the type-method naming style. +We chose `version_since` since it can read as "for versions since rust 1.20", follows the type-method naming style, help with version-related auto-completions, and version-related predicates will naturally sort next to each other. However, the name `version_since` can seem excessively verbose, especially compared to `version` or `since`. We could swap the order of parameters and make `rust` a default for the second parameter to allow `#[cfg(version_since("1.95"))]` as a shorthand. From d089ddd6dbe59c17ed71e36c75c3e512df13d8f9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 25 Sep 2025 14:32:39 -0500 Subject: [PATCH 109/109] feat(alt): Cover more operator concerns --- text/3857-cfg-version.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/text/3857-cfg-version.md b/text/3857-cfg-version.md index 9ebc1d6c248..b45ab6d958a 100644 --- a/text/3857-cfg-version.md +++ b/text/3857-cfg-version.md @@ -626,6 +626,15 @@ though this may be relaxed, see [cargo#14305](https://github.com/rust-lang/cargo This does not fit with out use cases because it causes discontinuities while users of the `cfg` need continuity. +[RFC #3796](https://github.com/rust-lang/rfcs/pull/3796) +does not address questions around binary operators, +requiring us to work it out. +For example, are the operands fully swappable? +If not, that could lead to impedance mismatches for users. +What all operators do we support? +All `Ord` operators increases the scope. +Not having all can lead to impedance mismatches for users. + This splits our specific predicate name (`version_since`) into smaller parts without losing conveyed meaning. This also provides a natural home for common documentation for cfg version concepts, if we gain more predicates.