From edf0059ebb7328d9b8a3552a818d40c82c47c686 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Sun, 30 Mar 2025 18:47:08 -0400 Subject: [PATCH 1/4] Start cfg-logical-ops --- text/0000-cfg-logical-ops.md | 94 ++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 text/0000-cfg-logical-ops.md diff --git a/text/0000-cfg-logical-ops.md b/text/0000-cfg-logical-ops.md new file mode 100644 index 00000000000..fb257044f42 --- /dev/null +++ b/text/0000-cfg-logical-ops.md @@ -0,0 +1,94 @@ +- Feature Name: `cfg_logical_ops` +- Start Date: 2025-03-30 +- 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 + +`#[cfg]`, `#[cfg_attr]`, and `cfg!()` can use `&&`, `||`, and `!` for `all`, `any`, and `not`, +respectively. + +# Motivation +[motivation]: #motivation + +While there are no technical restrictions to using logical operators, this was not always the case. +In Rust 1.0, attributes could not contain arbitrary tokens. This restriction was lifted in Rust +1.34, but the `cfg` syntax was not updated to take advantage of this. By letting developers use +logical operators, we are _lessening_ the burden of having to remember the `cfg` syntax. + +# Explanation +[explanation]: #explanation +[cfg-syntax]: https://doc.rust-lang.org/reference/conditional-compilation.html#r-cfg.syntax + +`#[cfg(foo && bar)]` enables the annotated code if and only if both `foo` **and** `bar` are enabled. +Similarly, `#[cfg(foo || bar)]` enables the annotated code if and only if either `foo` **or** `bar` +is enabled. Finally, `#[cfg(!foo)]` enables the annotated code if and only if `foo` is **not** +enabled. `#[cfg_attr]` and `cfg!()` behave the same way. Precedence is the same as in expressions. + +In terms of formal syntax, the [`[cfg.syntax]`][cfg-syntax] is changed to the following: + +> **Syntax**\ +> _ConfigurationPredicate_ :\ +>       _ConfigurationOption_\ +>    | _ConfigurationAll_\ +>    | _ConfigurationAny_\ +>    | _ConfigurationNot_\ +>    | _ConfigurationAnd_\ +>    | _ConfigurationOr_\ +>    | _ConfigurationNotOption_\ +>    | `(` _ConfigurationPredicate_ `)` +> +> _ConfigurationOption_ :\ +>    [IDENTIFIER] (`=` ([STRING_LITERAL] | [RAW_STRING_LITERAL]))? +> +> _ConfigurationAll_\ +>    `all` `(` _ConfigurationPredicateList_? `)` +> +> _ConfigurationAny_\ +>    `any` `(` _ConfigurationPredicateList_? `)` +> +> _ConfigurationNot_\ +>    (`not` | `!`) `(` _ConfigurationPredicate_ `)` +> +> _ConfigurationAnd_\ +>    _ConfigurationPredicateList_ `&&` _ConfigurationPredicateList_ +> +> _ConfigurationOr_\ +>    _ConfigurationPredicateList_ `||` _ConfigurationPredicateList_ +> +> _ConfigurationNotOption_\ +>    `!` _ConfigurationOption_ +> +> _ConfigurationPredicateList_\ +>    _ConfigurationPredicate_ (`,` _ConfigurationPredicate_)\* `,`? + +# Drawbacks +[drawbacks]: #drawbacks + +- Two ways to express the same thing. This can be somewhat mitigated by a lint for the old syntax. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- The current syntax is verbose and a relic of the past when attributes could not contain arbitrary + tokens. +- Using existing, widely-understood operators makes the syntax more familiar. + +# Prior art +[prior-art]: #prior-art + +The `efg` crate is nearly identical to this proposal, the sole difference being not requiring `=` +for key-value pairs. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +None so far. + +# Future possibilities +[future-possibilities]: #future-possibilities + +- Pattern-like syntax such as `#[cfg(feature = "foo" | "bar")]` could be allowed as a shorthand for + `#[cfg(feature = "foo" || feature = "bar")]`. This would be particularly useful for + platform-specific code (e.g. `#[cfg(target_os = "linux" | "windows")]`). From b32e356443f90ee0025dd12de4b2b601aedab313 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Mon, 31 Mar 2025 00:42:49 -0400 Subject: [PATCH 2/4] Fix mistake in and/or syntax --- text/0000-cfg-logical-ops.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-cfg-logical-ops.md b/text/0000-cfg-logical-ops.md index fb257044f42..72aa6b39e26 100644 --- a/text/0000-cfg-logical-ops.md +++ b/text/0000-cfg-logical-ops.md @@ -52,10 +52,10 @@ In terms of formal syntax, the [`[cfg.syntax]`][cfg-syntax] is changed to the fo >    (`not` | `!`) `(` _ConfigurationPredicate_ `)` > > _ConfigurationAnd_\ ->    _ConfigurationPredicateList_ `&&` _ConfigurationPredicateList_ +>    _ConfigurationPredicate_ `&&` _ConfigurationPredicate_ > > _ConfigurationOr_\ ->    _ConfigurationPredicateList_ `||` _ConfigurationPredicateList_ +>    _ConfigurationPredicate_ `||` _ConfigurationPredicate_ > > _ConfigurationNotOption_\ >    `!` _ConfigurationOption_ From 7c87c3690314f0416913bdf74dbdd3bdae6d49cd Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Mon, 31 Mar 2025 16:44:05 -0400 Subject: [PATCH 3/4] update from feedback --- text/0000-cfg-logical-ops.md | 65 ++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/text/0000-cfg-logical-ops.md b/text/0000-cfg-logical-ops.md index 72aa6b39e26..c96a5c277ef 100644 --- a/text/0000-cfg-logical-ops.md +++ b/text/0000-cfg-logical-ops.md @@ -1,6 +1,6 @@ - Feature Name: `cfg_logical_ops` - Start Date: 2025-03-30 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3796](https://github.com/rust-lang/rfcs/pull/3796) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary @@ -20,13 +20,46 @@ logical operators, we are _lessening_ the burden of having to remember the `cfg` # Explanation [explanation]: #explanation [cfg-syntax]: https://doc.rust-lang.org/reference/conditional-compilation.html#r-cfg.syntax +[precedence]: https://doc.rust-lang.org/reference/expressions.html#expression-precedence `#[cfg(foo && bar)]` enables the annotated code if and only if both `foo` **and** `bar` are enabled. Similarly, `#[cfg(foo || bar)]` enables the annotated code if and only if either `foo` **or** `bar` is enabled. Finally, `#[cfg(!foo)]` enables the annotated code if and only if `foo` is **not** -enabled. `#[cfg_attr]` and `cfg!()` behave the same way. Precedence is the same as in expressions. - -In terms of formal syntax, the [`[cfg.syntax]`][cfg-syntax] is changed to the following: +enabled. `#[cfg_attr]` and `cfg!()` behave the same way. + +Precedence is the [same as in expressions][precedence]. + +## Examples + +| Syntax | Equivalent to | Rationale | +| ---------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `a && b` | `all(a, b)` | definition of `&&` | +| `a \|\| b` | `any(a, b)` | definition of `\|\|` | +| `!a` | `not(a)` | definition of `!` | +| `(a)` | `a` | definition of `()` | +| `a && b && c && d` | `all(a, b, c, d)` (or `all(all(all(a, b), c), d)`) | `&&` is associative | +| `a \|\| b \|\| c \|\| d` | `any(a, b, c, d)` (or `any(any(any(a, b), c), d)`) | `\|\|` is associative | +| `!!!!!!a` | `not(not(not(not(not(not(a))))))` | `!` can be repeated | +| `((((((a))))))` | a | `()` can be nested | +| `a && b \|\| c && d` | `any(all(a, b), all(c, d))` | `\|\|` has lower precedence than `&&` | +| `a \|\| b && c \|\| d` | `any(a, all(b, c), d)` | `\|\|` has lower precedence than `&&` | +| `(a \|\| b) && (c \|\| d)` | `all(any(a, b), any(c, d))` | `()` can be used for grouping | +| `!a \|\| !b && !c` | `any(not(a), all(not(b), not(c)))` | `!` has highest precedence | +| `feature="foo" \|\| feature="bar"` | `any(feature="foo", feature="bar")` | `\|\|` has lower precedence than `=` | +| `feature="foo" && feature="bar"` | `all(feature="foo", feature="bar")` | `&&` has lower precedence than `=` | +| `!feature="foo"` | _syntax error_ | `!` has higher precedence than `=`, which may be confusing, so we ban this syntax | +| `!(feature="foo")` | `not(feature="foo")` | use `()` for grouping | +| `!all(x, y)` | `not(all(x, y))` | `!` has lower precedence than "function call" | +| `any(!x \|\| !w, !(y && z))` | `any(any(not(x), not(w)), not(all(y, z)))` | `!`, `&&` etc. can be used inside `any`, `all` and `not` | +| `true && !false` | `all(true, not(false))` | `!`, `&&` etc. can be used on boolean literals (they are syntactically identifiers) | +| `!accessible(std::mem::forget)` | `not(accessible(std::mem::forget))` | `!`, `&&` etc. can be used on `cfg_accessible` | +| `accessible(std::a \|\| std::b)` | _syntax error_ | … but not inside | +| `!version("1.42.0")` | `not(version("1.42.0"))` | `!`, `&&` etc. can be used on `cfg_version` | +| `version(!"1.42.0")` | _syntax error_ | … but not inside | + +## Formal syntax + +[`[cfg.syntax]`][cfg-syntax] is changed to the following: > **Syntax**\ > _ConfigurationPredicate_ :\ @@ -36,9 +69,20 @@ In terms of formal syntax, the [`[cfg.syntax]`][cfg-syntax] is changed to the fo >    | _ConfigurationNot_\ >    | _ConfigurationAnd_\ >    | _ConfigurationOr_\ ->    | _ConfigurationNotOption_\ +>    | _ConfigurationNegation_\ +>    | `(` _ConfigurationPredicate_ `)` +> +> _ConfigurationNegatable_ :\ +>    _ConfigurationOptionIdent_\ +>    | _ConfigurationAll_\ +>    | _ConfigurationAny_\ +>    | _ConfigurationNot_\ +>    | _ConfigurationNegation_ \ >    | `(` _ConfigurationPredicate_ `)` > +> _ConfigurationOptionIdent_ :\ +>    [IDENTIFIER] +> > _ConfigurationOption_ :\ >    [IDENTIFIER] (`=` ([STRING_LITERAL] | [RAW_STRING_LITERAL]))? > @@ -57,12 +101,15 @@ In terms of formal syntax, the [`[cfg.syntax]`][cfg-syntax] is changed to the fo > _ConfigurationOr_\ >    _ConfigurationPredicate_ `||` _ConfigurationPredicate_ > -> _ConfigurationNotOption_\ ->    `!` _ConfigurationOption_ +> _ConfigurationNegation_\ +>    `!` _ConfigurationNegatable_ > > _ConfigurationPredicateList_\ >    _ConfigurationPredicate_ (`,` _ConfigurationPredicate_)\* `,`? +All future function-like predicates (such as `version` and `accessible`) should be added to +_ConfigurationNegatable_. + # Drawbacks [drawbacks]: #drawbacks @@ -74,6 +121,10 @@ In terms of formal syntax, the [`[cfg.syntax]`][cfg-syntax] is changed to the fo - The current syntax is verbose and a relic of the past when attributes could not contain arbitrary tokens. - Using existing, widely-understood operators makes the syntax more familiar. +- `&` and `|` could be used instead of `&&` and `||`. Short-circuiting behavior is unobservable in + this context, so the behavior would be the same. +- `feature != "foo"` could be allowed as shorthand for `!(feature = "foo")`. This could plausibly be + interpreted as "any feature except 'foo'", which is why it is not included in this proposal. # Prior art [prior-art]: #prior-art From 9dd2445d1b138f6c6cf98b6ecd864a8273711ff2 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Mon, 31 Mar 2025 17:19:36 -0400 Subject: [PATCH 4/4] clarify `=` precedence --- text/0000-cfg-logical-ops.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/0000-cfg-logical-ops.md b/text/0000-cfg-logical-ops.md index c96a5c277ef..ecdb8279d79 100644 --- a/text/0000-cfg-logical-ops.md +++ b/text/0000-cfg-logical-ops.md @@ -27,7 +27,8 @@ Similarly, `#[cfg(foo || bar)]` enables the annotated code if and only if either is enabled. Finally, `#[cfg(!foo)]` enables the annotated code if and only if `foo` is **not** enabled. `#[cfg_attr]` and `cfg!()` behave the same way. -Precedence is the [same as in expressions][precedence]. +Precedence is the [same as in expressions][precedence], with `=` being treated as `==` for this +purpose. ## Examples