about summary refs log tree commit diff
diff options
context:
space:
mode:
authorUrgau <urgau@numericable.fr>2023-05-01 14:17:56 +0200
committerUrgau <urgau@numericable.fr>2023-10-13 13:34:21 +0200
commite5e95eba457b17a1f8005403fa0287e1cd8d82f3 (patch)
tree9cb5a4e065201ae300016981179cd0e449a127cc
parenta4a10bdf29a13b8773948c8938d2af47c03becb9 (diff)
downloadrust-e5e95eba457b17a1f8005403fa0287e1cd8d82f3.tar.gz
rust-e5e95eba457b17a1f8005403fa0287e1cd8d82f3.zip
MCP636: Add simpler and more explicit syntax to check-cfg
This add a new form and deprecated the other ones:
 - cfg(name1, ..., nameN, values("value1", "value2", ... "valueN"))
 - cfg(name1, ..., nameN) or cfg(name1, ..., nameN, values())
 - cfg(any())

It also changes the default exhaustiveness to be enable-by-default in
the presence of any --check-cfg arguments.
-rw-r--r--compiler/rustc_interface/src/interface.rs139
-rw-r--r--compiler/rustc_interface/src/lib.rs1
-rw-r--r--src/doc/rustdoc/src/unstable-features.md4
-rw-r--r--src/doc/unstable-book/src/compiler-flags/check-cfg.md216
4 files changed, 268 insertions, 92 deletions
diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs
index 1c330c064ab..65ef4a1442c 100644
--- a/compiler/rustc_interface/src/interface.rs
+++ b/compiler/rustc_interface/src/interface.rs
@@ -124,8 +124,13 @@ pub fn parse_cfgspecs(
 /// Converts strings provided as `--check-cfg [specs]` into a `CheckCfg`.
 pub fn parse_check_cfg(handler: &EarlyErrorHandler, specs: Vec<String>) -> CheckCfg {
     rustc_span::create_default_session_if_not_set_then(move |_| {
-        let mut check_cfg = CheckCfg::default();
+        // If any --check-cfg is passed then exhaustive_values and exhaustive_names
+        // are enabled by default.
+        let exhaustive_names = !specs.is_empty();
+        let exhaustive_values = !specs.is_empty();
+        let mut check_cfg = CheckCfg { exhaustive_names, exhaustive_values, ..CheckCfg::default() };
 
+        let mut old_syntax = None;
         for s in specs {
             let sess = ParseSess::with_silent_emitter(Some(format!(
                 "this error occurred on the command line: `--check-cfg={s}`"
@@ -141,18 +146,21 @@ pub fn parse_check_cfg(handler: &EarlyErrorHandler, specs: Vec<String>) -> Check
                 };
             }
 
-            let expected_error = || {
-                error!(
-                    "expected `names(name1, name2, ... nameN)` or \
-                        `values(name, \"value1\", \"value2\", ... \"valueN\")`"
-                )
-            };
+            let expected_error =
+                || error!("expected `cfg(name, values(\"value1\", \"value2\", ... \"valueN\"))`");
 
             match maybe_new_parser_from_source_str(&sess, filename, s.to_string()) {
                 Ok(mut parser) => match parser.parse_meta_item() {
                     Ok(meta_item) if parser.token == token::Eof => {
                         if let Some(args) = meta_item.meta_item_list() {
                             if meta_item.has_name(sym::names) {
+                                // defaults are flipped for the old syntax
+                                if old_syntax == None {
+                                    check_cfg.exhaustive_names = false;
+                                    check_cfg.exhaustive_values = false;
+                                }
+                                old_syntax = Some(true);
+
                                 check_cfg.exhaustive_names = true;
                                 for arg in args {
                                     if arg.is_word() && arg.ident().is_some() {
@@ -166,6 +174,13 @@ pub fn parse_check_cfg(handler: &EarlyErrorHandler, specs: Vec<String>) -> Check
                                     }
                                 }
                             } else if meta_item.has_name(sym::values) {
+                                // defaults are flipped for the old syntax
+                                if old_syntax == None {
+                                    check_cfg.exhaustive_names = false;
+                                    check_cfg.exhaustive_values = false;
+                                }
+                                old_syntax = Some(true);
+
                                 if let Some((name, values)) = args.split_first() {
                                     if name.is_word() && name.ident().is_some() {
                                         let ident = name.ident().expect("multi-segment cfg key");
@@ -215,6 +230,116 @@ pub fn parse_check_cfg(handler: &EarlyErrorHandler, specs: Vec<String>) -> Check
                                 } else {
                                     expected_error();
                                 }
+                            } else if meta_item.has_name(sym::cfg) {
+                                old_syntax = Some(false);
+
+                                let mut names = Vec::new();
+                                let mut values: FxHashSet<_> = Default::default();
+
+                                let mut any_specified = false;
+                                let mut values_specified = false;
+                                let mut values_any_specified = false;
+
+                                for arg in args {
+                                    if arg.is_word() && let Some(ident) = arg.ident() {
+                                        if values_specified {
+                                            error!("`cfg()` names cannot be after values");
+                                        }
+                                        names.push(ident);
+                                    } else if arg.has_name(sym::any)
+                                        && let Some(args) = arg.meta_item_list()
+                                    {
+                                        if any_specified {
+                                            error!("`any()` cannot be specified multiple times");
+                                        }
+                                        any_specified = true;
+                                        if !args.is_empty() {
+                                            error!("`any()` must be empty");
+                                        }
+                                    } else if arg.has_name(sym::values)
+                                        && let Some(args) = arg.meta_item_list()
+                                    {
+                                        if names.is_empty() {
+                                            error!(
+                                                "`values()` cannot be specified before the names"
+                                            );
+                                        } else if values_specified {
+                                            error!(
+                                                "`values()` cannot be specified multiple times"
+                                            );
+                                        }
+                                        values_specified = true;
+
+                                        for arg in args {
+                                            if let Some(LitKind::Str(s, _)) =
+                                                arg.lit().map(|lit| &lit.kind)
+                                            {
+                                                values.insert(Some(s.to_string()));
+                                            } else if arg.has_name(sym::any)
+                                                && let Some(args) = arg.meta_item_list()
+                                            {
+                                                if values_any_specified {
+                                                    error!(
+                                                        "`any()` in `values()` cannot be specified multiple times"
+                                                    );
+                                                }
+                                                values_any_specified = true;
+                                                if !args.is_empty() {
+                                                    error!("`any()` must be empty");
+                                                }
+                                            } else {
+                                                error!(
+                                                    "`values()` arguments must be string literals or `any()`"
+                                                );
+                                            }
+                                        }
+                                    } else {
+                                        error!(
+                                            "`cfg()` arguments must be simple identifiers, `any()` or `values(...)`"
+                                        );
+                                    }
+                                }
+
+                                if values.is_empty() && !values_any_specified && !any_specified {
+                                    values.insert(None);
+                                } else if !values.is_empty() && values_any_specified {
+                                    error!(
+                                        "`values()` arguments cannot specify string literals and `any()` at the same time"
+                                    );
+                                }
+
+                                if any_specified {
+                                    if !names.is_empty()
+                                        || !values.is_empty()
+                                        || values_any_specified
+                                    {
+                                        error!("`cfg(any())` can only be provided in isolation");
+                                    }
+
+                                    check_cfg.exhaustive_names = false;
+                                } else {
+                                    for name in names {
+                                        check_cfg
+                                            .expecteds
+                                            .entry(name.to_string())
+                                            .and_modify(|v| match v {
+                                                ExpectedValues::Some(v)
+                                                    if !values_any_specified =>
+                                                {
+                                                    v.extend(values.clone())
+                                                }
+                                                ExpectedValues::Some(_) => *v = ExpectedValues::Any,
+                                                ExpectedValues::Any => {}
+                                            })
+                                            .or_insert_with(|| {
+                                                if values_any_specified {
+                                                    ExpectedValues::Any
+                                                } else {
+                                                    ExpectedValues::Some(values.clone())
+                                                }
+                                            });
+                                    }
+                                }
                             } else {
                                 expected_error();
                             }
diff --git a/compiler/rustc_interface/src/lib.rs b/compiler/rustc_interface/src/lib.rs
index 76131c1ad69..ffa2667a351 100644
--- a/compiler/rustc_interface/src/lib.rs
+++ b/compiler/rustc_interface/src/lib.rs
@@ -3,6 +3,7 @@
 #![feature(internal_output_capture)]
 #![feature(thread_spawn_unchecked)]
 #![feature(lazy_cell)]
+#![feature(let_chains)]
 #![feature(try_blocks)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index 199b5d69a1c..41602dec44c 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -628,10 +628,10 @@ Using this flag looks like this:
 
 ```bash
 $ rustdoc src/lib.rs -Z unstable-options \
-    --check-cfg='names()' --check-cfg='values(feature, "foo", "bar")'
+    --check-cfg='cfg(feature, values("foo", "bar"))'
 ```
 
-The example above check every well known names (`target_os`, `doc`, `test`, ... via `names()`)
+The example above check every well known names and values (`target_os`, `doc`, `test`, ...)
 and check the values of `feature`: `foo` and `bar`.
 
 ### `--generate-link-to-definition`: Generate links on types in source code
diff --git a/src/doc/unstable-book/src/compiler-flags/check-cfg.md b/src/doc/unstable-book/src/compiler-flags/check-cfg.md
index 10f0fbc5062..ca18ec567a4 100644
--- a/src/doc/unstable-book/src/compiler-flags/check-cfg.md
+++ b/src/doc/unstable-book/src/compiler-flags/check-cfg.md
@@ -10,97 +10,80 @@ This feature allows you to enable complete or partial checking of configuration.
 check them. The `--check-cfg` option takes a value, called the _check cfg specification_. The
 check cfg specification is parsed using the Rust metadata syntax, just as the `--cfg` option is.
 
-`--check-cfg` option can take one of two forms:
+`--check-cfg` option take one form:
 
-1. `--check-cfg names(...)` enables checking condition names.
-2. `--check-cfg values(...)` enables checking the values within list-valued conditions.
-
-These two options are independent. `names` checks only the namespace of condition names
-while `values` checks only the namespace of the values of list-valued conditions.
+1. `--check-cfg cfg(...)` enables checking the values within list-valued conditions.
 
 NOTE: No implicit expectation is added when using `--cfg` for both forms. Users are expected to
-pass all expected names and values using `names(...)` and `values(...)`.
+pass all expected names and values using `cfg(...)`.
 
-## The `names(...)` form
+## The `cfg(...)` form
 
-The `names(...)` form enables checking the names. This form uses a named list:
+The `cfg(...)` form enables checking the values within list-valued conditions. It has this
+basic form:
 
 ```bash
-rustc --check-cfg 'names(name1, name2, ... nameN)'
+rustc --check-cfg 'cfg(name1, ..., nameN, values("value1", "value2", ... "valueN"))'
 ```
 
-where each `name` is a bare identifier (has no quotes). The order of the names is not significant.
+where `name` is a bare identifier (has no quotes) and each `"value"` term is a quoted literal
+string. `name` specifies the name of the condition, such as `feature` or `my_cfg`.
 
-If `--check-cfg names(...)` is specified at least once, then `rustc` will check all references to
-condition names. `rustc` will check every `#[cfg]` attribute, `#[cfg_attr]` attribute, `cfg` clause
-inside `#[link]` attribute and `cfg!(...)` call against the provided list of expected condition
-names. If a name is not present in this list, then `rustc` will report an `unexpected_cfgs` lint
-diagnostic. The default diagnostic level for this lint is `Warn`.
+When the `cfg(...)` option is specified, `rustc` will check every `#[cfg(name = "value")]`
+attribute, `#[cfg_attr(name = "value")]` attribute, `#[link(name = "a", cfg(name = "value"))]`
+and `cfg!(name = "value")` call. It will check that the `"value"` specified is present in the
+list of expected values. If `"value"` is not in it, then `rustc` will report an `unexpected_cfgs`
+lint diagnostic. The default diagnostic level for this lint is `Warn`.
 
-If `--check-cfg names(...)` is not specified, then `rustc` will not check references to condition
-names.
+To enable checking of values, but to provide an empty set of expected values, use these forms:
 
-`--check-cfg names(...)` may be specified more than once. The result is that the list of valid
-condition names is merged across all options. It is legal for a condition name to be specified
-more than once; redundantly specifying a condition name has no effect.
+```bash
+rustc --check-cfg 'cfg(name1, ..., nameN)'
+rustc --check-cfg 'cfg(name1, ..., nameN, values())'
+```
 
-To enable checking condition names with an empty set of valid condition names, use the following
-form. The parentheses are required.
+To enable checking of name but not values (i.e. unknown expected values), use this form:
 
 ```bash
-rustc --check-cfg 'names()'
+rustc --check-cfg 'cfg(name1, ..., nameN, values(any()))'
 ```
 
-Note that `--check-cfg 'names()'` is _not_ equivalent to omitting the option entirely.
-The first form enables checking condition names, while specifying that there are no valid
-condition names (outside of the set of well-known names defined by `rustc`). Omitting the
-`--check-cfg 'names(...)'` option does not enable checking condition names.
-
-## The `values(...)` form
+The `--check-cfg cfg(...)` option can be repeated, both for the same condition name and for
+different names. If it is repeated for the same condition name, then the sets of values for that
+condition are merged together (presedence is given to `any()`).
 
-The `values(...)` form enables checking the values within list-valued conditions. It has this
-form:
+## Well known names and values
 
-```bash
-rustc --check-cfg `values(name, "value1", "value2", ... "valueN")'
-```
+`rustc` has a internal list of well known names and their corresponding values.
+Those well known names and values follows the same stability as what they refer to.
 
-where `name` is a bare identifier (has no quotes) and each `"value"` term is a quoted literal
-string. `name` specifies the name of the condition, such as `feature` or `target_os`.
+Well known values checking is always enabled as long as a `--check-cfg` argument is present.
 
-When the `values(...)` option is specified, `rustc` will check every `#[cfg(name = "value")]`
-attribute, `#[cfg_attr(name = "value")]` attribute, `#[link(name = "a", cfg(name = "value"))]`
-and `cfg!(name = "value")` call. It will check that the `"value"` specified is present in the
-list of expected values. If `"value"` is not in it, then `rustc` will report an `unexpected_cfgs`
-lint diagnostic. The default diagnostic level for this lint is `Warn`.
+Well known names checking is always enable as long as a `--check-cfg` argument is present
+**unless** any `cfg(any())` argument is passed.
 
-To enable checking of values, but to provide an empty set of valid values, use this form:
+To disable checking of well known names, use this form:
 
 ```bash
-rustc --check-cfg `values(name)`
+rustc --check-cfg 'cfg(any())'
 ```
 
-The `--check-cfg values(...)` option can be repeated, both for the same condition name and for
-different names. If it is repeated for the same condition name, then the sets of values for that
-condition are merged together.
-
-If `values()` is specified, then `rustc` will enable the checking of well-known values defined
-by itself. Note that it's necessary to specify the `values()` form to enable the checking of
-well known values, specifying the other forms doesn't implicitly enable it.
+NOTE: If one want to enable values and names checking without having any cfg to declare, one
+can use an empty `cfg()` argument.
 
 ## Examples
 
 Consider this command line:
 
 ```bash
-rustc --check-cfg 'names(feature)' \
-      --check-cfg 'values(feature, "lion", "zebra")' \
+rustc --check-cfg 'cfg(feature, values("lion", "zebra"))' \
       --cfg 'feature="lion"' -Z unstable-options \
       example.rs
 ```
 
 This command line indicates that this crate has two features: `lion` and `zebra`. The `lion`
-feature is enabled, while the `zebra` feature is disabled. Consider compiling this code:
+feature is enabled, while the `zebra` feature is disabled. Exhaustive checking of names and
+values are enabled by default. Consider compiling this code:
 
 ```rust
 // This is expected, and tame_lion() will be compiled
@@ -119,35 +102,36 @@ fn poke_platypus() {}
 // and will cause a compiler warning (by default).
 #[cfg(feechure = "lion")]
 fn tame_lion() {}
-```
 
-> Note: The `--check-cfg names(feature)` option is necessary only to enable checking the condition
-> name, as in the last example. `feature` is a well-known (always-expected) condition name, and so
-> it is not necessary to specify it in a `--check-cfg 'names(...)'` option. That option can be
-> shortened to > `--check-cfg names()` in order to enable checking well-known condition names.
+// This is UNEXPECTED, because 'windows' is a well known condition name,
+// and because 'windows' doens't take any values,
+// and will cause a compiler warning (by default).
+#[cfg(windows = "unix")]
+fn tame_windows() {}
+```
 
 ### Example: Checking condition names, but not values
 
 ```bash
 # This turns on checking for condition names, but not values, such as 'feature' values.
-rustc --check-cfg 'names(is_embedded, has_feathers)' \
+rustc --check-cfg 'cfg(is_embedded, has_feathers, values(any()))' \
       --cfg has_feathers -Z unstable-options
 ```
 
 ```rust
-#[cfg(is_embedded)]         // This is expected as "is_embedded" was provided in names()
-fn do_embedded() {}
+#[cfg(is_embedded)]         // This is expected as "is_embedded" was provided in cfg()
+fn do_embedded() {}         // and because names exhaustiveness was not disabled
 
-#[cfg(has_feathers)]        // This is expected as "has_feathers" was provided in names()
-fn do_features() {}
+#[cfg(has_feathers)]        // This is expected as "has_feathers" was provided in cfg()
+fn do_features() {}         // and because names exhaustiveness was not disbaled
 
-#[cfg(has_feathers = "zapping")] // This is expected as "has_feathers" was provided in names()
+#[cfg(has_feathers = "zapping")] // This is expected as "has_feathers" was provided in cfg()
                                  // and because no value checking was enable for "has_feathers"
                                  // no warning is emitted for the value "zapping"
 fn do_zapping() {}
 
 #[cfg(has_mumble_frotz)]    // This is UNEXPECTED because names checking is enable and
-                            // "has_mumble_frotz" was not provided in names()
+                            // "has_mumble_frotz" was not provided in cfg()
 fn do_mumble_frotz() {}
 ```
 
@@ -155,25 +139,25 @@ fn do_mumble_frotz() {}
 
 ```bash
 # This turns on checking for feature values, but not for condition names.
-rustc --check-cfg 'values(feature, "zapping", "lasers")' \
+rustc --check-cfg 'configure(feature, values("zapping", "lasers"))' \
+      --check-cfg 'cfg(any())' \
       --cfg 'feature="zapping"' -Z unstable-options
 ```
 
 ```rust
-#[cfg(is_embedded)]         // This is doesn't raise a warning, because names checking was not
-                            // enable (ie not names())
+#[cfg(is_embedded)]         // This is doesn't raise a warning, because names checking was
+                            // disabled by 'cfg(any())'
 fn do_embedded() {}
 
-#[cfg(has_feathers)]        // Same as above, --check-cfg names(...) was never used so no name
+#[cfg(has_feathers)]        // Same as above, 'cfg(any())' was provided so no name
                             // checking is performed
 fn do_features() {}
 
-
-#[cfg(feature = "lasers")]  // This is expected, "lasers" is in the values(feature) list
+#[cfg(feature = "lasers")]  // This is expected, "lasers" is in the cfg(feature) list
 fn shoot_lasers() {}
 
 #[cfg(feature = "monkeys")] // This is UNEXPECTED, because "monkeys" is not in the
-                            // --check-cfg values(feature) list
+                            // cfg(feature) list
 fn write_shakespeare() {}
 ```
 
@@ -181,26 +165,92 @@ fn write_shakespeare() {}
 
 ```bash
 # This turns on checking for feature values and for condition names.
-rustc --check-cfg 'names(is_embedded, has_feathers)' \
-      --check-cfg 'values(feature, "zapping", "lasers")' \
+rustc --check-cfg 'cfg(is_embedded, has_feathers)' \
+      --check-cfg 'cfg(feature, values("zapping", "lasers"))' \
       --cfg has_feathers --cfg 'feature="zapping"' -Z unstable-options
 ```
 
 ```rust
-#[cfg(is_embedded)]         // This is expected because "is_embedded" was provided in names()
-fn do_embedded() {}
+#[cfg(is_embedded)]         // This is expected because "is_embedded" was provided in cfg()
+fn do_embedded() {}         // and doesn't take any value
 
-#[cfg(has_feathers)]        // This is expected because "has_feathers" was provided in names()
-fn do_features() {}
+#[cfg(has_feathers)]        // This is expected because "has_feathers" was provided in cfg()
+fn do_features() {}         // and deosn't take any value
 
-#[cfg(has_mumble_frotz)]    // This is UNEXPECTED, because has_mumble_frotz is not in the
-                            // --check-cfg names(...) list
+#[cfg(has_mumble_frotz)]    // This is UNEXPECTED, because "has_mumble_frotz" was never provided
 fn do_mumble_frotz() {}
 
-#[cfg(feature = "lasers")]  // This is expected, "lasers" is in the values(feature) list
+#[cfg(feature = "lasers")]  // This is expected, "lasers" is in the cfg(feature) list
 fn shoot_lasers() {}
 
 #[cfg(feature = "monkeys")] // This is UNEXPECTED, because "monkeys" is not in
-                            // the values(feature) list
+                            // the cfg(feature) list
 fn write_shakespeare() {}
 ```
+
+## The deprecated `names(...)` form
+
+The `names(...)` form enables checking the names. This form uses a named list:
+
+```bash
+rustc --check-cfg 'names(name1, name2, ... nameN)'
+```
+
+where each `name` is a bare identifier (has no quotes). The order of the names is not significant.
+
+If `--check-cfg names(...)` is specified at least once, then `rustc` will check all references to
+condition names. `rustc` will check every `#[cfg]` attribute, `#[cfg_attr]` attribute, `cfg` clause
+inside `#[link]` attribute and `cfg!(...)` call against the provided list of expected condition
+names. If a name is not present in this list, then `rustc` will report an `unexpected_cfgs` lint
+diagnostic. The default diagnostic level for this lint is `Warn`.
+
+If `--check-cfg names(...)` is not specified, then `rustc` will not check references to condition
+names.
+
+`--check-cfg names(...)` may be specified more than once. The result is that the list of valid
+condition names is merged across all options. It is legal for a condition name to be specified
+more than once; redundantly specifying a condition name has no effect.
+
+To enable checking condition names with an empty set of valid condition names, use the following
+form. The parentheses are required.
+
+```bash
+rustc --check-cfg 'names()'
+```
+
+Note that `--check-cfg 'names()'` is _not_ equivalent to omitting the option entirely.
+The first form enables checking condition names, while specifying that there are no valid
+condition names (outside of the set of well-known names defined by `rustc`). Omitting the
+`--check-cfg 'names(...)'` option does not enable checking condition names.
+
+## The deprecated `values(...)` form
+
+The `values(...)` form enables checking the values within list-valued conditions. It has this
+form:
+
+```bash
+rustc --check-cfg `values(name, "value1", "value2", ... "valueN")'
+```
+
+where `name` is a bare identifier (has no quotes) and each `"value"` term is a quoted literal
+string. `name` specifies the name of the condition, such as `feature` or `target_os`.
+
+When the `values(...)` option is specified, `rustc` will check every `#[cfg(name = "value")]`
+attribute, `#[cfg_attr(name = "value")]` attribute, `#[link(name = "a", cfg(name = "value"))]`
+and `cfg!(name = "value")` call. It will check that the `"value"` specified is present in the
+list of expected values. If `"value"` is not in it, then `rustc` will report an `unexpected_cfgs`
+lint diagnostic. The default diagnostic level for this lint is `Warn`.
+
+To enable checking of values, but to provide an empty set of valid values, use this form:
+
+```bash
+rustc --check-cfg `values(name)`
+```
+
+The `--check-cfg values(...)` option can be repeated, both for the same condition name and for
+different names. If it is repeated for the same condition name, then the sets of values for that
+condition are merged together.
+
+If `values()` is specified, then `rustc` will enable the checking of well-known values defined
+by itself. Note that it's necessary to specify the `values()` form to enable the checking of
+well known values, specifying the other forms doesn't implicitly enable it.