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, ) -> (Vec, 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, 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 { 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 { 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 = 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::>(); possibilities.sort_by_key(|s| s.as_str()); let get_possibilities_sub = || { if !possibilities.is_empty() { let possibilities = possibilities.iter().copied().cloned().collect::>().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 = 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()), } }