about summary refs log tree commit diff
path: root/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_lint/src/early/diagnostics/check_cfg.rs')
-rw-r--r--compiler/rustc_lint/src/early/diagnostics/check_cfg.rs362
1 files changed, 362 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs b/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs
new file mode 100644
index 00000000000..63a722f6067
--- /dev/null
+++ b/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs
@@ -0,0 +1,362 @@
+use rustc_hir::def_id::LOCAL_CRATE;
+use rustc_middle::bug;
+use rustc_session::Session;
+use rustc_session::config::ExpectedValues;
+use rustc_span::edit_distance::find_best_match_for_name;
+use rustc_span::symbol::Ident;
+use rustc_span::{ExpnKind, Span, Symbol, sym};
+
+use crate::lints;
+
+const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35;
+
+fn sort_and_truncate_possibilities(
+    sess: &Session,
+    mut possibilities: Vec<Symbol>,
+) -> (Vec<Symbol>, usize) {
+    let n_possibilities = if sess.opts.unstable_opts.check_cfg_all_expected {
+        possibilities.len()
+    } else {
+        std::cmp::min(possibilities.len(), MAX_CHECK_CFG_NAMES_OR_VALUES)
+    };
+
+    possibilities.sort_by(|s1, s2| s1.as_str().cmp(s2.as_str()));
+
+    let and_more = possibilities.len().saturating_sub(n_possibilities);
+    possibilities.truncate(n_possibilities);
+    (possibilities, and_more)
+}
+
+enum EscapeQuotes {
+    Yes,
+    No,
+}
+
+fn to_check_cfg_arg(name: Ident, value: Option<Symbol>, quotes: EscapeQuotes) -> String {
+    if let Some(value) = value {
+        let value = str::escape_debug(value.as_str()).to_string();
+        let values = match quotes {
+            EscapeQuotes::Yes => format!("\\\"{}\\\"", value.replace("\"", "\\\\\\\\\"")),
+            EscapeQuotes::No => format!("\"{value}\""),
+        };
+        format!("cfg({name}, values({values}))")
+    } else {
+        format!("cfg({name})")
+    }
+}
+
+fn cargo_help_sub(
+    sess: &Session,
+    inst: &impl Fn(EscapeQuotes) -> String,
+) -> lints::UnexpectedCfgCargoHelp {
+    // We don't want to suggest the `build.rs` way to expected cfgs if we are already in a
+    // `build.rs`. We therefor do a best effort check (looking if the `--crate-name` is
+    // `build_script_build`) to try to figure out if we are building a Cargo build script
+
+    let unescaped = &inst(EscapeQuotes::No);
+    if matches!(&sess.opts.crate_name, Some(crate_name) if crate_name == "build_script_build") {
+        lints::UnexpectedCfgCargoHelp::lint_cfg(unescaped)
+    } else {
+        lints::UnexpectedCfgCargoHelp::lint_cfg_and_build_rs(unescaped, &inst(EscapeQuotes::Yes))
+    }
+}
+
+fn rustc_macro_help(span: Span) -> Option<lints::UnexpectedCfgRustcMacroHelp> {
+    let oexpn = span.ctxt().outer_expn_data();
+    if let Some(def_id) = oexpn.macro_def_id
+        && let ExpnKind::Macro(macro_kind, macro_name) = oexpn.kind
+        && def_id.krate != LOCAL_CRATE
+    {
+        Some(lints::UnexpectedCfgRustcMacroHelp { macro_kind: macro_kind.descr(), macro_name })
+    } else {
+        None
+    }
+}
+
+fn cargo_macro_help(span: Span) -> Option<lints::UnexpectedCfgCargoMacroHelp> {
+    let oexpn = span.ctxt().outer_expn_data();
+    if let Some(def_id) = oexpn.macro_def_id
+        && let ExpnKind::Macro(macro_kind, macro_name) = oexpn.kind
+        && def_id.krate != LOCAL_CRATE
+    {
+        Some(lints::UnexpectedCfgCargoMacroHelp {
+            macro_kind: macro_kind.descr(),
+            macro_name,
+            // FIXME: Get access to a `TyCtxt` from an `EarlyContext`
+            // crate_name: cx.tcx.crate_name(def_id.krate),
+        })
+    } else {
+        None
+    }
+}
+
+pub(super) fn unexpected_cfg_name(
+    sess: &Session,
+    (name, name_span): (Symbol, Span),
+    value: Option<(Symbol, Span)>,
+) -> lints::UnexpectedCfgName {
+    #[allow(rustc::potential_query_instability)]
+    let possibilities: Vec<Symbol> = sess.psess.check_config.expecteds.keys().copied().collect();
+
+    let mut names_possibilities: Vec<_> = if value.is_none() {
+        // We later sort and display all the possibilities, so the order here does not matter.
+        #[allow(rustc::potential_query_instability)]
+        sess.psess
+            .check_config
+            .expecteds
+            .iter()
+            .filter_map(|(k, v)| match v {
+                ExpectedValues::Some(v) if v.contains(&Some(name)) => Some(k),
+                _ => None,
+            })
+            .collect()
+    } else {
+        Vec::new()
+    };
+
+    let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
+    let is_from_external_macro = rustc_middle::lint::in_external_macro(sess, name_span);
+    let mut is_feature_cfg = name == sym::feature;
+
+    let code_sugg = if is_feature_cfg && is_from_cargo {
+        lints::unexpected_cfg_name::CodeSuggestion::DefineFeatures
+    // Suggest the most probable if we found one
+    } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
+        is_feature_cfg |= best_match == sym::feature;
+
+        if let Some(ExpectedValues::Some(best_match_values)) =
+            sess.psess.check_config.expecteds.get(&best_match)
+        {
+            // We will soon sort, so the initial order does not matter.
+            #[allow(rustc::potential_query_instability)]
+            let mut possibilities = best_match_values.iter().flatten().collect::<Vec<_>>();
+            possibilities.sort_by_key(|s| s.as_str());
+
+            let get_possibilities_sub = || {
+                if !possibilities.is_empty() {
+                    let possibilities =
+                        possibilities.iter().copied().cloned().collect::<Vec<_>>().into();
+                    Some(lints::unexpected_cfg_name::ExpectedValues { best_match, possibilities })
+                } else {
+                    None
+                }
+            };
+
+            let best_match = Ident::new(best_match, name_span);
+            if let Some((value, value_span)) = value {
+                if best_match_values.contains(&Some(value)) {
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameAndValue {
+                        span: name_span,
+                        code: best_match.to_string(),
+                    }
+                } else if best_match_values.contains(&None) {
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameNoValue {
+                        span: name_span.to(value_span),
+                        code: best_match.to_string(),
+                    }
+                } else if let Some(first_value) = possibilities.first() {
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
+                        span: name_span.to(value_span),
+                        code: format!("{best_match} = \"{first_value}\""),
+                        expected: get_possibilities_sub(),
+                    }
+                } else {
+                    lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
+                        span: name_span.to(value_span),
+                        code: best_match.to_string(),
+                        expected: get_possibilities_sub(),
+                    }
+                }
+            } else {
+                lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
+                    span: name_span,
+                    code: best_match.to_string(),
+                    expected: get_possibilities_sub(),
+                }
+            }
+        } else {
+            lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
+                span: name_span,
+                code: best_match.to_string(),
+                expected: None,
+            }
+        }
+    } else {
+        let similar_values = if !names_possibilities.is_empty() && names_possibilities.len() <= 3 {
+            names_possibilities.sort();
+            names_possibilities
+                .iter()
+                .map(|cfg_name| lints::unexpected_cfg_name::FoundWithSimilarValue {
+                    span: name_span,
+                    code: format!("{cfg_name} = \"{name}\""),
+                })
+                .collect()
+        } else {
+            vec![]
+        };
+        let expected_names = if !possibilities.is_empty() {
+            let (possibilities, and_more) = sort_and_truncate_possibilities(sess, possibilities);
+            let possibilities: Vec<_> =
+                possibilities.into_iter().map(|s| Ident::new(s, name_span)).collect();
+            Some(lints::unexpected_cfg_name::ExpectedNames {
+                possibilities: possibilities.into(),
+                and_more,
+            })
+        } else {
+            None
+        };
+        lints::unexpected_cfg_name::CodeSuggestion::SimilarValues {
+            with_similar_values: similar_values,
+            expected_names,
+        }
+    };
+
+    let inst = |escape_quotes| {
+        to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
+    };
+
+    let invocation_help = if is_from_cargo {
+        let help = if !is_feature_cfg && !is_from_external_macro {
+            Some(cargo_help_sub(sess, &inst))
+        } else {
+            None
+        };
+        lints::unexpected_cfg_name::InvocationHelp::Cargo {
+            help,
+            macro_help: cargo_macro_help(name_span),
+        }
+    } else {
+        let help = lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No));
+        lints::unexpected_cfg_name::InvocationHelp::Rustc {
+            help,
+            macro_help: rustc_macro_help(name_span),
+        }
+    };
+
+    lints::UnexpectedCfgName { code_sugg, invocation_help, name }
+}
+
+pub(super) fn unexpected_cfg_value(
+    sess: &Session,
+    (name, name_span): (Symbol, Span),
+    value: Option<(Symbol, Span)>,
+) -> lints::UnexpectedCfgValue {
+    let Some(ExpectedValues::Some(values)) = &sess.psess.check_config.expecteds.get(&name) else {
+        bug!(
+            "it shouldn't be possible to have a diagnostic on a value whose name is not in values"
+        );
+    };
+    let mut have_none_possibility = false;
+    // We later sort possibilities if it is not empty, so the
+    // order here does not matter.
+    #[allow(rustc::potential_query_instability)]
+    let possibilities: Vec<Symbol> = values
+        .iter()
+        .inspect(|a| have_none_possibility |= a.is_none())
+        .copied()
+        .flatten()
+        .collect();
+
+    let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
+    let is_from_external_macro = rustc_middle::lint::in_external_macro(sess, name_span);
+
+    // 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 expected_values = {
+            let (possibilities, and_more) =
+                sort_and_truncate_possibilities(sess, possibilities.clone());
+            lints::unexpected_cfg_value::ExpectedValues {
+                name,
+                have_none_possibility,
+                possibilities: possibilities.into(),
+                and_more,
+            }
+        };
+
+        let suggestion = if let Some((value, value_span)) = value {
+            // Suggest the most probable if we found one
+            if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
+                Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SimilarName {
+                    span: value_span,
+                    best_match,
+                })
+            } else {
+                None
+            }
+        } else if let &[first_possibility] = &possibilities[..] {
+            Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue {
+                span: name_span.shrink_to_hi(),
+                first_possibility,
+            })
+        } else {
+            None
+        };
+
+        lints::unexpected_cfg_value::CodeSuggestion::ChangeValue { expected_values, suggestion }
+    } else if have_none_possibility {
+        let suggestion =
+            value.map(|(_value, value_span)| lints::unexpected_cfg_value::RemoveValueSuggestion {
+                span: name_span.shrink_to_hi().to(value_span),
+            });
+        lints::unexpected_cfg_value::CodeSuggestion::RemoveValue { suggestion, name }
+    } else {
+        let span = if let Some((_value, value_span)) = value {
+            name_span.to(value_span)
+        } else {
+            name_span
+        };
+        let suggestion = lints::unexpected_cfg_value::RemoveConditionSuggestion { span };
+        lints::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name }
+    };
+
+    // We don't want to encourage people to add values to a well-known names, as these are
+    // defined by rustc/Rust itself. Users can still do this if they wish, but should not be
+    // encouraged to do so.
+    let can_suggest_adding_value = !sess.psess.check_config.well_known_names.contains(&name)
+        // Except when working on rustc or the standard library itself, in which case we want to
+        // suggest adding these cfgs to the "normal" place because of bootstrapping reasons. As a
+        // basic heuristic, we use the "cheat" unstable feature enable method and the
+        // non-ui-testing enabled option.
+        || (matches!(sess.psess.unstable_features, rustc_feature::UnstableFeatures::Cheat)
+            && !sess.opts.unstable_opts.ui_testing);
+
+    let inst = |escape_quotes| {
+        to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
+    };
+
+    let invocation_help = if is_from_cargo {
+        let help = if name == sym::feature && !is_from_external_macro {
+            if let Some((value, _value_span)) = value {
+                Some(lints::unexpected_cfg_value::CargoHelp::AddFeature { value })
+            } else {
+                Some(lints::unexpected_cfg_value::CargoHelp::DefineFeatures)
+            }
+        } else if can_suggest_adding_value && !is_from_external_macro {
+            Some(lints::unexpected_cfg_value::CargoHelp::Other(cargo_help_sub(sess, &inst)))
+        } else {
+            None
+        };
+        lints::unexpected_cfg_value::InvocationHelp::Cargo {
+            help,
+            macro_help: cargo_macro_help(name_span),
+        }
+    } else {
+        let help = if can_suggest_adding_value {
+            Some(lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No)))
+        } else {
+            None
+        };
+        lints::unexpected_cfg_value::InvocationHelp::Rustc {
+            help,
+            macro_help: rustc_macro_help(name_span),
+        }
+    };
+
+    lints::UnexpectedCfgValue {
+        code_sugg,
+        invocation_help,
+        has_value: value.is_some(),
+        value: value.map_or_else(String::new, |(v, _span)| v.to_string()),
+    }
+}