Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Overhaul the #[cfg(..)] pattern syntax #194

Merged
merged 3 commits into from
Sep 23, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions active/0000-cfg-syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
- Start Date: 2014-08-09
- RFC PR:
- Rust Issue:

# Summary

The `#[cfg(...)]` attribute provides a mechanism for conditional compilation of
items in a Rust crate. This RFC proposes to change the syntax of `#[cfg]` to
make more sense as well as enable expansion of the conditional compilation
system to attributes while maintaining a single syntax.

# Motivation

In the current implementation, `#[cfg(...)]` takes a comma separated list of
`key`, `key = "value"`, `not(key)`, or `not(key = "value")`. An individual
`#[cfg(...)]` attribute "matches" if *all* of the contained cfg patterns match
the compilation environment, and an item preserved if it *either* has no
`#[cfg(...)]` attributes or *any* of the `#[cfg(...)]` attributes present
match.

This is problematic for several reasons:

* It is excessively verbose in certain situations. For example, implementing
the equivalent of `(a AND (b OR c OR d))` requires three separate
attributes and `a` to be duplicated in each.
* It differs from all other attributes in that all `#[cfg(...)]` attributes on
an item must be processed together instead of in isolation. This change
will move `#[cfg(...)]` closer to implementation as a normal syntax
extension.

# Detailed design

The `<p>` inside of `#[cfg(<p>)]` will be called a *cfg pattern* and have a
simple recursive syntax:

* `key` is a cfg pattern and will match if `key` is present in the
compilation environment.
* `key = "value"` is a cfg pattern and will match if a mapping from `key`
to `value` is present in the compilation environment. At present, key-value
pairs only exist for compiler defined keys such as `target_os` and
`endian`.
* `not(<p>)` is a cfg pattern if `<p>` is and matches if `<p>` does not match.
* `all(<p>, ...)` is a cfg pattern if all of the comma-separated `<p>`s are cfg
patterns and all of them match.
* `any(<p>, ...)` is a cfg pattern if all of the comma-separated `<p>`s are cfg
patterns and any of them match.

If an item is tagged with `#[cfg(<p>)]`, that item will be stripped from the
AST if the cfg pattern `<p>` does not match.

One implementation hazard is that the semantics of
```rust
#[cfg(a)]
#[cfg(b)]
fn foo() {}
```
will change from "include `foo` if *either of* `a` and `b` are present in the
compilation environment" to "include `foo` if *both of* `a` and `b` are present
in the compilation environment". To ease the transition, the old semantics of
multiple `#[cfg(...)]` attributes will be maintained as a special case, with a
warning. After some reasonable period of time, the special case will be
removed.

In addition, `#[cfg(a, b, c)]` will be accepted with a warning and be
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this give three ways to write all? #[cfg(a, b)] #[cfg(a)] #[cfg(b)] and #[cfg(all(a, b))]?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but the first and second behaviors will trigger deprecation warnings.

Edit: Actually, the second case will in the short term be equivalent to #[cfg(any(a, b)], again with a warning.

equivalent to `#[cfg(all(a, b, c))]`. Again, after some reasonable period of
time, this behavior will be removed as well.

The `cfg!()` syntax extension will be modified to accept cfg patterns as well.
A `#[cfg_attr(<p>, <attr>)]` syntax extension will be added
([PR 16230](https://github.com/rust-lang/rust/pull/16230)) which will expand to
`#[<attr>]` if the cfg pattern `<p>` matches. The test harness's
`#[ignore]` attribute will have its built-in cfg filtering
functionality stripped in favor of `#[cfg_attr(<p>, ignore)]`.

# Drawbacks

While the implementation of this change in the compiler will be
straightforward, the effects on downstream code will be significant, especially
in the standard library.

# Alternatives

`all` and `any` could be renamed to `and` and `or`, though I feel that the
proposed names read better with the function-like syntax and are consistent
with `Iterator::all` and `Iterator::any`.

Issue [#2119](https://github.com/rust-lang/rust/issues/2119) proposed the
addition of `||` and `&&` operators and parantheses to the attribute syntax
to result in something like `#[cfg(a || (b && c)]`. I don't favor this proposal
since it would result in a major change to the attribute syntax for relatively
little readability gain.

# Unresolved questions

How long should multiple `#[cfg(...)]` attributes on a single item be
forbidden? It should probably be at least until after 0.12 releases.

Should we permanently keep the behavior of treating `#[cfg(a, b)]` as
`#[cfg(all(a, b))]`? It is the common case, and adding this interpretation
can reduce the noise level a bit. On the other hand, it may be a bit confusing
to read as it's not immediately clear if it will be processed as `and(..)` or
`all(..)`.