From e5e95eba457b17a1f8005403fa0287e1cd8d82f3 Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 1 May 2023 14:17:56 +0200 Subject: [PATCH] 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. --- compiler/rustc_interface/src/interface.rs | 139 +++++++- compiler/rustc_interface/src/lib.rs | 1 + src/doc/rustdoc/src/unstable-features.md | 4 +- .../src/compiler-flags/check-cfg.md | 302 ++++++++++-------- 4 files changed, 311 insertions(+), 135 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) -> 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) -> 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) -> 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) -> 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,18 +10,185 @@ 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 `cfg(...)` form enables checking the values within list-valued conditions. It has this +basic form: + +```bash +rustc --check-cfg 'cfg(name1, ..., nameN, values("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 `my_cfg`. + +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`. + +To enable checking of values, but to provide an empty set of expected values, use these forms: + +```bash +rustc --check-cfg 'cfg(name1, ..., nameN)' +rustc --check-cfg 'cfg(name1, ..., nameN, values())' +``` + +To enable checking of name but not values (i.e. unknown expected values), use this form: + +```bash +rustc --check-cfg 'cfg(name1, ..., nameN, values(any()))' +``` + +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()`). + +## Well known names and values + +`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. + +Well known values checking is always enabled as long as a `--check-cfg` argument is present. + +Well known names checking is always enable as long as a `--check-cfg` argument is present +**unless** any `cfg(any())` argument is passed. + +To disable checking of well known names, use this form: + +```bash +rustc --check-cfg 'cfg(any())' +``` + +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 '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. Exhaustive checking of names and +values are enabled by default. Consider compiling this code: + +```rust +// This is expected, and tame_lion() will be compiled +#[cfg(feature = "lion")] +fn tame_lion(lion: Lion) {} + +// This is expected, and ride_zebra() will NOT be compiled. +#[cfg(feature = "zebra")] +fn ride_zebra(zebra: Zebra) {} + +// This is UNEXPECTED, and will cause a compiler warning (by default). +#[cfg(feature = "platypus")] +fn poke_platypus() {} + +// This is UNEXPECTED, because 'feechure' is not a known condition name, +// and will cause a compiler warning (by default). +#[cfg(feechure = "lion")] +fn tame_lion() {} + +// 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 '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 cfg() +fn do_embedded() {} // and because names exhaustiveness was not disabled + +#[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 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 cfg() +fn do_mumble_frotz() {} +``` + +### Example: Checking feature values, but not condition names + +```bash +# This turns on checking for feature values, but not for condition names. +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 + // disabled by 'cfg(any())' +fn do_embedded() {} + +#[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 cfg(feature) list +fn shoot_lasers() {} + +#[cfg(feature = "monkeys")] // This is UNEXPECTED, because "monkeys" is not in the + // cfg(feature) list +fn write_shakespeare() {} +``` + +### Example: Checking both condition names and feature values + +```bash +# This turns on checking for feature values and for condition names. +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 cfg() +fn do_embedded() {} // and doesn't take any value + +#[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" was never provided +fn do_mumble_frotz() {} + +#[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 cfg(feature) list +fn write_shakespeare() {} +``` + +## The deprecated `names(...)` form The `names(...)` form enables checking the names. This form uses a named list: @@ -56,7 +223,7 @@ The first form enables checking condition names, while specifying that there are 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 deprecated `values(...)` form The `values(...)` form enables checking the values within list-valued conditions. It has this form: @@ -87,120 +254,3 @@ 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. - -## Examples - -Consider this command line: - -```bash -rustc --check-cfg 'names(feature)' \ - --check-cfg 'values(feature, "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: - -```rust -// This is expected, and tame_lion() will be compiled -#[cfg(feature = "lion")] -fn tame_lion(lion: Lion) {} - -// This is expected, and ride_zebra() will NOT be compiled. -#[cfg(feature = "zebra")] -fn ride_zebra(zebra: Zebra) {} - -// This is UNEXPECTED, and will cause a compiler warning (by default). -#[cfg(feature = "platypus")] -fn poke_platypus() {} - -// This is UNEXPECTED, because 'feechure' is not a known condition name, -// 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. - -### 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)' \ - --cfg has_feathers -Z unstable-options -``` - -```rust -#[cfg(is_embedded)] // This is expected as "is_embedded" was provided in names() -fn do_embedded() {} - -#[cfg(has_feathers)] // This is expected as "has_feathers" was provided in names() -fn do_features() {} - -#[cfg(has_feathers = "zapping")] // This is expected as "has_feathers" was provided in names() - // 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() -fn do_mumble_frotz() {} -``` - -### Example: Checking feature values, but not condition names - -```bash -# This turns on checking for feature values, but not for condition names. -rustc --check-cfg 'values(feature, "zapping", "lasers")' \ - --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()) -fn do_embedded() {} - -#[cfg(has_feathers)] // Same as above, --check-cfg names(...) was never used so no name - // checking is performed -fn do_features() {} - - -#[cfg(feature = "lasers")] // This is expected, "lasers" is in the values(feature) list -fn shoot_lasers() {} - -#[cfg(feature = "monkeys")] // This is UNEXPECTED, because "monkeys" is not in the - // --check-cfg values(feature) list -fn write_shakespeare() {} -``` - -### Example: Checking both condition names and feature values - -```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")' \ - --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(has_feathers)] // This is expected because "has_feathers" was provided in names() -fn do_features() {} - -#[cfg(has_mumble_frotz)] // This is UNEXPECTED, because has_mumble_frotz is not in the - // --check-cfg names(...) list -fn do_mumble_frotz() {} - -#[cfg(feature = "lasers")] // This is expected, "lasers" is in the values(feature) list -fn shoot_lasers() {} - -#[cfg(feature = "monkeys")] // This is UNEXPECTED, because "monkeys" is not in - // the values(feature) list -fn write_shakespeare() {} -```