Repository: rust-lang/rust
Pull request: `#[cfg]`: suggest alternative `target_` name when the value does not match

<!-- homu-ignore:start -->
*[View all comments](https://triagebot.infra.rust-lang.org/gh-comments/rust-lang/rust/pull/154777)*
<!-- homu-ignore:end -->

<!-- homu-ignore:start -->
<!--
If this PR is related to an unstable feature or an otherwise tracked effort,
please link to the relevant tracking issue here. If you don't know of a related
tracking issue or there are none, feel free to ignore this.

This PR will get automatically assigned to a reviewer. In case you would like
a specific user to review your work, you can assign it to them by using

    r? <reviewer name>
-->

Renders as:
![out](https://github.com/user-attachments/assets/cf28257d-8aaf-4576-91f6-9419e816a91a)

<!-- homu-ignore:end -->


Changed files:
filename: compiler/rustc_lint/src/early/diagnostics/check_cfg.rs
patch: @@ -316,9 +316,27 @@ pub(super) fn unexpected_cfg_value(
     let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
     let is_from_external_macro = name_span.in_external_macro(sess.source_map());
 
-    // Show the full list if all possible values for a given name, but don't do it
-    // for names as the possibilities could be very long
-    let code_sugg = if !possibilities.is_empty() {
+    let code_sugg = if let Some((value, _)) = value
+        && sess.psess.check_config.well_known_names.contains(&name)
+        && let valid_names = possible_well_known_names_for_cfg_value(sess, value)
+        && !valid_names.is_empty()
+    {
+        // Suggest changing the name to something for which `value` is an expected value.
+        let max_suggestions = 3;
+        let suggestions = valid_names
+            .iter()
+            .take(max_suggestions)
+            .copied()
+            .map(|name| lints::unexpected_cfg_value::ChangeNameSuggestion {
+                span: name_span,
+                name,
+                value,
+            })
+            .collect::<Vec<_>>();
+        lints::unexpected_cfg_value::CodeSuggestion::ChangeName { suggestions }
+    } else if !possibilities.is_empty() {
+        // Show the full list if all possible values for a given name, but don't do it
+        // for names as the possibilities could be very long
         let expected_values = {
             let (possibilities, and_more) = sort_and_truncate_possibilities(
                 sess,
@@ -419,3 +437,22 @@ pub(super) fn unexpected_cfg_value(
         value: value.map_or_else(String::new, |(v, _span)| v.to_string()),
     }
 }
+
+/// Ordering of the output is not stable, use this only in diagnostic code.
+fn possible_well_known_names_for_cfg_value(sess: &Session, value: Symbol) -> Vec<Symbol> {
+    #[allow(rustc::potential_query_instability)]
+    sess.psess
+        .check_config
+        .well_known_names
+        .iter()
+        .filter(|name| {
+            sess.psess
+                .check_config
+                .expecteds
+                .get(*name)
+                .map(|expected_values| expected_values.contains(&Some(value)))
+                .unwrap_or_default()
+        })
+        .copied()
+        .collect()
+}

filename: compiler/rustc_lint/src/lints.rs
patch: @@ -2904,6 +2904,10 @@ pub(crate) mod unexpected_cfg_value {
 
             name: Symbol,
         },
+        ChangeName {
+            #[subdiagnostic]
+            suggestions: Vec<ChangeNameSuggestion>,
+        },
     }
 
     #[derive(Subdiagnostic)]
@@ -2961,6 +2965,20 @@ pub(crate) mod unexpected_cfg_value {
         pub and_more: usize,
     }
 
+    #[derive(Subdiagnostic)]
+    #[suggestion(
+        "`{$value}` is an expected value for `{$name}`",
+        code = "{name}",
+        applicability = "maybe-incorrect",
+        style = "verbose"
+    )]
+    pub(crate) struct ChangeNameSuggestion {
+        #[primary_span]
+        pub span: Span,
+        pub name: Symbol,
+        pub value: Symbol,
+    }
+
     #[derive(Subdiagnostic)]
     pub(crate) enum InvocationHelp {
         #[note(

filename: compiler/rustc_session/src/config/cfg.rs
patch: @@ -65,6 +65,13 @@ impl<T: Eq + Hash> ExpectedValues<T> {
             ExpectedValues::Any => false,
         }
     }
+
+    pub fn contains(&self, value: &Option<T>) -> bool {
+        match self {
+            ExpectedValues::Some(expecteds) => expecteds.contains(value),
+            ExpectedValues::Any => false,
+        }
+    }
 }
 
 impl<T: Eq + Hash> Extend<T> for ExpectedValues<T> {

filename: tests/ui/cfg/suggest-alternative-name-on-target.rs
patch: @@ -0,0 +1,39 @@
+#![deny(unexpected_cfgs)]
+//~^ NOTE lint level is defined here
+
+// target arch used in `target_abi`
+#[cfg(target_abi = "arm")]
+//~^ ERROR unexpected `cfg` condition value:
+//~| NOTE see <https://doc.rust-lang.org
+//~| HELP `arm` is an expected value for `target_arch`
+struct A;
+
+// target env used in `target_arch`
+#[cfg(target_arch = "gnu")]
+//~^ ERROR unexpected `cfg` condition value:
+//~| NOTE see <https://doc.rust-lang.org
+//~| HELP `gnu` is an expected value for `target_env`
+struct B;
+
+// target os used in `target_env`
+#[cfg(target_env = "openbsd")]
+//~^ ERROR unexpected `cfg` condition value:
+//~| NOTE see <https://doc.rust-lang.org
+//~| HELP `openbsd` is an expected value for `target_os`
+struct C;
+
+// target abi used in `target_os`
+#[cfg(target_os = "eabi")]
+//~^ ERROR unexpected `cfg` condition value:
+//~| NOTE see <https://doc.rust-lang.org
+//~| HELP `eabi` is an expected value for `target_abi`
+struct D;
+
+#[cfg(target_abi = "windows")]
+//~^ ERROR unexpected `cfg` condition value:
+//~| NOTE see <https://doc.rust-lang.org
+//~| HELP `windows` is an expected value for `target_os`
+//~| HELP `windows` is an expected value for `target_family`
+struct E;
+
+fn main() {}

filename: tests/ui/cfg/suggest-alternative-name-on-target.stderr
patch: @@ -0,0 +1,77 @@
+error: unexpected `cfg` condition value: `arm`
+  --> $DIR/suggest-alternative-name-on-target.rs:5:7
+   |
+LL | #[cfg(target_abi = "arm")]
+   |       ^^^^^^^^^^^^^^^^^^
+   |
+   = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
+note: the lint level is defined here
+  --> $DIR/suggest-alternative-name-on-target.rs:1:9
+   |
+LL | #![deny(unexpected_cfgs)]
+   |         ^^^^^^^^^^^^^^^
+help: `arm` is an expected value for `target_arch`
+   |
+LL - #[cfg(target_abi = "arm")]
+LL + #[cfg(target_arch = "arm")]
+   |
+
+error: unexpected `cfg` condition value: `gnu`
+  --> $DIR/suggest-alternative-name-on-target.rs:12:7
+   |
+LL | #[cfg(target_arch = "gnu")]
+   |       ^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
+help: `gnu` is an expected value for `target_env`
+   |
+LL - #[cfg(target_arch = "gnu")]
+LL + #[cfg(target_env = "gnu")]
+   |
+
+error: unexpected `cfg` condition value: `openbsd`
+  --> $DIR/suggest-alternative-name-on-target.rs:19:7
+   |
+LL | #[cfg(target_env = "openbsd")]
+   |       ^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
+help: `openbsd` is an expected value for `target_os`
+   |
+LL - #[cfg(target_env = "openbsd")]
+LL + #[cfg(target_os = "openbsd")]
+   |
+
+error: unexpected `cfg` condition value: `eabi`
+  --> $DIR/suggest-alternative-name-on-target.rs:26:7
+   |
+LL | #[cfg(target_os = "eabi")]
+   |       ^^^^^^^^^^^^^^^^^^
+   |
+   = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
+help: `eabi` is an expected value for `target_abi`
+   |
+LL - #[cfg(target_os = "eabi")]
+LL + #[cfg(target_abi = "eabi")]
+   |
+
+error: unexpected `cfg` condition value: `windows`
+  --> $DIR/suggest-alternative-name-on-target.rs:32:7
+   |
+LL | #[cfg(target_abi = "windows")]
+   |       ^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
+help: `windows` is an expected value for `target_os`
+   |
+LL - #[cfg(target_abi = "windows")]
+LL + #[cfg(target_os = "windows")]
+   |
+help: `windows` is an expected value for `target_family`
+   |
+LL - #[cfg(target_abi = "windows")]
+LL + #[cfg(target_family = "windows")]
+   |
+
+error: aborting due to 5 previous errors
+