diff --git a/text/0000-cfg-logical-ops.md b/text/0000-cfg-logical-ops.md new file mode 100644 index 00000000000..ecdb8279d79 --- /dev/null +++ b/text/0000-cfg-logical-ops.md @@ -0,0 +1,146 @@ +- Feature Name: `cfg_logical_ops` +- Start Date: 2025-03-30 +- 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 +[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 +[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][precedence], with `=` being treated as `==` for this +purpose. + +## 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_ :\ +>       _ConfigurationOption_\ +>    | _ConfigurationAll_\ +>    | _ConfigurationAny_\ +>    | _ConfigurationNot_\ +>    | _ConfigurationAnd_\ +>    | _ConfigurationOr_\ +>    | _ConfigurationNegation_\ +>    | `(` _ConfigurationPredicate_ `)` +> +> _ConfigurationNegatable_ :\ +>    _ConfigurationOptionIdent_\ +>    | _ConfigurationAll_\ +>    | _ConfigurationAny_\ +>    | _ConfigurationNot_\ +>    | _ConfigurationNegation_ \ +>    | `(` _ConfigurationPredicate_ `)` +> +> _ConfigurationOptionIdent_ :\ +>    [IDENTIFIER] +> +> _ConfigurationOption_ :\ +>    [IDENTIFIER] (`=` ([STRING_LITERAL] | [RAW_STRING_LITERAL]))? +> +> _ConfigurationAll_\ +>    `all` `(` _ConfigurationPredicateList_? `)` +> +> _ConfigurationAny_\ +>    `any` `(` _ConfigurationPredicateList_? `)` +> +> _ConfigurationNot_\ +>    (`not` | `!`) `(` _ConfigurationPredicate_ `)` +> +> _ConfigurationAnd_\ +>    _ConfigurationPredicate_ `&&` _ConfigurationPredicate_ +> +> _ConfigurationOr_\ +>    _ConfigurationPredicate_ `||` _ConfigurationPredicate_ +> +> _ConfigurationNegation_\ +>    `!` _ConfigurationNegatable_ +> +> _ConfigurationPredicateList_\ +>    _ConfigurationPredicate_ (`,` _ConfigurationPredicate_)\* `,`? + +All future function-like predicates (such as `version` and `accessible`) should be added to +_ConfigurationNegatable_. + +# 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. +- `&` 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 + +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")]`).