use rustc_errors::{Applicability, Diag}; use rustc_session::{config::ExpectedValues, Session}; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::{sym, Span, Symbol}; const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35; fn check_cfg_expected_note( sess: &Session, possibilities: &[Symbol], type_: &str, name: Option, suffix: &str, ) -> String { use std::fmt::Write; 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) }; let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::>(); possibilities.sort(); let and_more = possibilities.len().saturating_sub(n_possibilities); let possibilities = possibilities[..n_possibilities].join("`, `"); let mut note = String::with_capacity(50 + possibilities.len()); write!(&mut note, "expected {type_}").unwrap(); if let Some(name) = name { write!(&mut note, " for `{name}`").unwrap(); } write!(&mut note, " are: {suffix}`{possibilities}`").unwrap(); if and_more > 0 { write!(&mut note, " and {and_more} more").unwrap(); } note } pub(super) fn unexpected_cfg_name( sess: &Session, diag: &mut Diag<'_, ()>, (name, name_span): (Symbol, Span), value: Option<(Symbol, Span)>, ) { #[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 mut is_feature_cfg = name == sym::feature; if is_feature_cfg && is_from_cargo { diag.help("consider defining some features in `Cargo.toml`"); // Suggest the most probable if we found one } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) { 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().map(Symbol::as_str).collect::>(); possibilities.sort(); let mut should_print_possibilities = true; if let Some((value, value_span)) = value { if best_match_values.contains(&Some(value)) { diag.span_suggestion( name_span, "there is a config with a similar name and value", best_match, Applicability::MaybeIncorrect, ); should_print_possibilities = false; } else if best_match_values.contains(&None) { diag.span_suggestion( name_span.to(value_span), "there is a config with a similar name and no value", best_match, Applicability::MaybeIncorrect, ); should_print_possibilities = false; } else if let Some(first_value) = possibilities.first() { diag.span_suggestion( name_span.to(value_span), "there is a config with a similar name and different values", format!("{best_match} = \"{first_value}\""), Applicability::MaybeIncorrect, ); } else { diag.span_suggestion( name_span.to(value_span), "there is a config with a similar name and different values", best_match, Applicability::MaybeIncorrect, ); }; } else { diag.span_suggestion( name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect, ); } if !possibilities.is_empty() && should_print_possibilities { let possibilities = possibilities.join("`, `"); diag.help(format!("expected values for `{best_match}` are: `{possibilities}`")); } } else { diag.span_suggestion( name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect, ); } is_feature_cfg |= best_match == sym::feature; } else { if !names_possibilities.is_empty() && names_possibilities.len() <= 3 { names_possibilities.sort(); for cfg_name in names_possibilities.iter() { diag.span_suggestion( name_span, "found config with similar value", format!("{cfg_name} = \"{name}\""), Applicability::MaybeIncorrect, ); } } if !possibilities.is_empty() { diag.help_once(check_cfg_expected_note(sess, &possibilities, "names", None, "")); } } let inst = if let Some((value, _value_span)) = value { let pre = if is_from_cargo { "\\" } else { "" }; format!("cfg({name}, values({pre}\"{value}{pre}\"))") } else { format!("cfg({name})") }; if is_from_cargo { if !is_feature_cfg { diag.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); } diag.note("see for more information about checking conditional configuration"); } else { diag.help(format!("to expect this configuration use `--check-cfg={inst}`")); diag.note("see for more information about checking conditional configuration"); } } pub(super) fn unexpected_cfg_value( sess: &Session, diag: &mut Diag<'_, ()>, (name, name_span): (Symbol, Span), value: Option<(Symbol, Span)>, ) { 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(); // 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 if !possibilities.is_empty() { diag.note(check_cfg_expected_note( sess, &possibilities, "values", Some(name), if have_none_possibility { "(none), " } else { "" }, )); 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) { diag.span_suggestion( value_span, "there is a expected value with a similar name", format!("\"{best_match}\""), Applicability::MaybeIncorrect, ); } } else if let &[first_possibility] = &possibilities[..] { diag.span_suggestion( name_span.shrink_to_hi(), "specify a config value", format!(" = \"{first_possibility}\""), Applicability::MaybeIncorrect, ); } } else if have_none_possibility { diag.note(format!("no expected value for `{name}`")); if let Some((_value, value_span)) = value { diag.span_suggestion( name_span.shrink_to_hi().to(value_span), "remove the value", "", Applicability::MaybeIncorrect, ); } } else { diag.note(format!("no expected values for `{name}`")); let sp = if let Some((_value, value_span)) = value { name_span.to(value_span) } else { name_span }; diag.span_suggestion(sp, "remove the condition", "", Applicability::MaybeIncorrect); } // We don't want to suggest adding values to well known names // since those are defined by rustc it-self. Users can still // do it if they want, but should not encourage them. let is_cfg_a_well_know_name = sess.psess.check_config.well_known_names.contains(&name); let inst = if let Some((value, _value_span)) = value { let pre = if is_from_cargo { "\\" } else { "" }; format!("cfg({name}, values({pre}\"{value}{pre}\"))") } else { format!("cfg({name})") }; if is_from_cargo { if name == sym::feature { if let Some((value, _value_span)) = value { diag.help(format!("consider adding `{value}` as a feature in `Cargo.toml`")); } else { diag.help("consider defining some features in `Cargo.toml`"); } } else if !is_cfg_a_well_know_name { diag.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); } diag.note("see for more information about checking conditional configuration"); } else { if !is_cfg_a_well_know_name { diag.help(format!("to expect this configuration use `--check-cfg={inst}`")); } diag.note("see for more information about checking conditional configuration"); } }