1
Fork 0

Convert unexpected_cfg_{name,value} to struct diagnostics

This commit is contained in:
Xiretza 2024-04-23 16:18:24 +00:00
parent bac6b6248b
commit 2482f3c17c
21 changed files with 474 additions and 190 deletions

View file

@ -1,44 +1,28 @@
use rustc_errors::{Applicability, Diag};
use rustc_errors::Diag;
use rustc_middle::bug;
use rustc_session::{config::ExpectedValues, Session};
use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::{sym, Span, Symbol};
use crate::lints;
const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35;
fn check_cfg_expected_note(
fn sort_and_truncate_possibilities(
sess: &Session,
possibilities: &[Symbol],
type_: &str,
name: Option<Symbol>,
suffix: &str,
) -> String {
use std::fmt::Write;
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)
};
let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>();
possibilities.sort();
possibilities.sort_by(|s1, s2| s1.as_str().cmp(s2.as_str()));
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
possibilities.truncate(n_possibilities);
(possibilities, and_more)
}
enum EscapeQuotes {
@ -87,106 +71,115 @@ pub(super) fn unexpected_cfg_name(
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`");
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().map(Symbol::as_str).collect::<Vec<_>>();
possibilities.sort();
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 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;
lints::unexpected_cfg_name::CodeSuggestion::SimilarNameAndValue {
span: name_span,
code: best_match.to_string(),
}
} 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;
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() {
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,
);
lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
span: name_span.to(value_span),
code: format!("{best_match} = \"{first_value}\""),
expected: get_possibilities_sub(),
}
} else {
diag.span_suggestion(
name_span.to(value_span),
"there is a config with a similar name and different values",
best_match,
Applicability::MaybeIncorrect,
);
};
lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
span: name_span.to(value_span),
code: best_match.to_string(),
expected: get_possibilities_sub(),
}
}
} 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}`"));
lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
span: name_span,
code: best_match.to_string(),
expected: get_possibilities_sub(),
}
}
} 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,
);
lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
span: name_span,
code: best_match.to_string(),
expected: None,
}
}
if !possibilities.is_empty() {
diag.help_once(check_cfg_expected_note(sess, &possibilities, "names", 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);
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(name, value.map(|(v, _s)| v), escape_quotes);
if is_from_cargo {
if !is_feature_cfg {
diag.help(format!("consider using a Cargo feature instead"));
diag.help(format!("or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:\n [lints.rust]\n unexpected_cfgs = {{ level = \"warn\", check-cfg = ['{}'] }}", inst(EscapeQuotes::No)));
diag.help(format!("or consider adding `println!(\"cargo::rustc-check-cfg={}\");` to the top of the `build.rs`", inst(EscapeQuotes::Yes)));
}
diag.note("see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration");
let invocation_help = if is_from_cargo {
let sub = if !is_feature_cfg {
Some(lints::UnexpectedCfgCargoHelp::new(
&inst(EscapeQuotes::No),
&inst(EscapeQuotes::Yes),
))
} else {
None
};
lints::unexpected_cfg_name::InvocationHelp::Cargo { sub }
} else {
let inst = inst(EscapeQuotes::No);
diag.help(format!("to expect this configuration use `--check-cfg={inst}`",));
diag.note("see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration");
}
lints::unexpected_cfg_name::InvocationHelp::Rustc(lints::UnexpectedCfgRustcHelp::new(
&inst(EscapeQuotes::No),
))
};
diag.subdiagnostic(diag.dcx, lints::UnexpectedCfgNameSub { code_sugg, invocation_help });
}
pub(super) fn unexpected_cfg_value(
@ -214,53 +207,53 @@ pub(super) fn unexpected_cfg_value(
// 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 { "" },
));
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,
}
};
if let Some((value, value_span)) = value {
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) {
diag.span_suggestion(
value_span,
"there is a expected value with a similar name",
format!("\"{best_match}\""),
Applicability::MaybeIncorrect,
);
Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SimilarName {
span: value_span,
best_match,
})
} else {
None
}
} 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}`"));
Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue {
span: name_span.shrink_to_hi(),
first_possibility,
})
} else {
None
};
let sp = if let Some((_value, value_span)) = value {
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
};
diag.span_suggestion(sp, "remove the condition", "", Applicability::MaybeIncorrect);
}
let suggestion = lints::unexpected_cfg_value::RemoveConditionSuggestion { span };
lints::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name }
};
// We don't want to suggest adding values to well known names
// since those are defined by rustc it-self. Users can still
@ -269,24 +262,30 @@ pub(super) fn unexpected_cfg_value(
let inst = |escape_quotes| to_check_cfg_arg(name, value.map(|(v, _s)| v), escape_quotes);
if is_from_cargo {
if name == sym::feature {
let invocation_help = if is_from_cargo {
let help = if name == sym::feature {
if let Some((value, _value_span)) = value {
diag.help(format!("consider adding `{value}` as a feature in `Cargo.toml`"));
Some(lints::unexpected_cfg_value::CargoHelp::AddFeature { value })
} else {
diag.help("consider defining some features in `Cargo.toml`");
Some(lints::unexpected_cfg_value::CargoHelp::DefineFeatures)
}
} else if !is_cfg_a_well_know_name {
diag.help(format!("consider using a Cargo feature instead"));
diag.help(format!("or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:\n [lints.rust]\n unexpected_cfgs = {{ level = \"warn\", check-cfg = ['{}'] }}", inst(EscapeQuotes::No)));
diag.help(format!("or consider adding `println!(\"cargo::rustc-check-cfg={}\");` to the top of the `build.rs`", inst(EscapeQuotes::Yes)));
}
diag.note("see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration");
Some(lints::unexpected_cfg_value::CargoHelp::Other(lints::UnexpectedCfgCargoHelp::new(
&inst(EscapeQuotes::No),
&inst(EscapeQuotes::Yes),
)))
} else {
None
};
lints::unexpected_cfg_value::InvocationHelp::Cargo(help)
} else {
if !is_cfg_a_well_know_name {
let inst = inst(EscapeQuotes::No);
diag.help(format!("to expect this configuration use `--check-cfg={inst}`",));
}
diag.note("see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration");
}
let help = if !is_cfg_a_well_know_name {
Some(lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No)))
} else {
None
};
lints::unexpected_cfg_value::InvocationHelp::Rustc(help)
};
diag.subdiagnostic(diag.dcx, lints::UnexpectedCfgValueSub { code_sugg, invocation_help });
}

View file

@ -1953,3 +1953,254 @@ pub struct UnitBindingsDiag {
#[help]
#[note]
pub struct BuiltinNamedAsmLabel;
#[derive(Subdiagnostic)]
#[help(lint_unexpected_cfg_add_cargo_feature)]
#[help(lint_unexpected_cfg_add_cargo_toml_lint_cfg)]
#[help(lint_unexpected_cfg_add_build_rs_println)]
pub struct UnexpectedCfgCargoHelp {
pub build_rs_println: String,
pub cargo_toml_lint_cfg: String,
}
impl UnexpectedCfgCargoHelp {
pub fn new(unescaped: &str, escaped: &str) -> Self {
Self {
cargo_toml_lint_cfg: format!(
"\n [lints.rust]\n unexpected_cfgs = {{ level = \"warn\", check-cfg = ['{unescaped}'] }}",
),
build_rs_println: format!("println!(\"cargo::rustc-check-cfg={escaped}\");",),
}
}
}
#[derive(Subdiagnostic)]
#[help(lint_unexpected_cfg_add_cmdline_arg)]
pub struct UnexpectedCfgRustcHelp {
pub cmdline_arg: String,
}
impl UnexpectedCfgRustcHelp {
pub fn new(unescaped: &str) -> Self {
Self { cmdline_arg: format!("--check-cfg={unescaped}") }
}
}
#[derive(Subdiagnostic)]
pub struct UnexpectedCfgNameSub {
#[subdiagnostic]
pub code_sugg: unexpected_cfg_name::CodeSuggestion,
#[subdiagnostic]
pub invocation_help: unexpected_cfg_name::InvocationHelp,
}
pub mod unexpected_cfg_name {
use rustc_errors::DiagSymbolList;
use rustc_macros::Subdiagnostic;
use rustc_span::{Span, Symbol};
#[derive(Subdiagnostic)]
pub enum CodeSuggestion {
#[help(lint_unexpected_cfg_define_features)]
DefineFeatures,
#[suggestion(
lint_unexpected_cfg_name_similar_name_value,
applicability = "maybe-incorrect",
code = "{code}"
)]
SimilarNameAndValue {
#[primary_span]
span: Span,
code: String,
},
#[suggestion(
lint_unexpected_cfg_name_similar_name_no_value,
applicability = "maybe-incorrect",
code = "{code}"
)]
SimilarNameNoValue {
#[primary_span]
span: Span,
code: String,
},
#[suggestion(
lint_unexpected_cfg_name_similar_name_different_values,
applicability = "maybe-incorrect",
code = "{code}"
)]
SimilarNameDifferentValues {
#[primary_span]
span: Span,
code: String,
#[subdiagnostic]
expected: Option<ExpectedValues>,
},
#[suggestion(
lint_unexpected_cfg_name_similar_name,
applicability = "maybe-incorrect",
code = "{code}"
)]
SimilarName {
#[primary_span]
span: Span,
code: String,
#[subdiagnostic]
expected: Option<ExpectedValues>,
},
SimilarValues {
#[subdiagnostic]
with_similar_values: Vec<FoundWithSimilarValue>,
#[subdiagnostic]
expected_names: Option<ExpectedNames>,
},
}
#[derive(Subdiagnostic)]
#[help(lint_unexpected_cfg_name_expected_values)]
pub struct ExpectedValues {
pub best_match: Symbol,
pub possibilities: DiagSymbolList,
}
#[derive(Subdiagnostic)]
#[suggestion(
lint_unexpected_cfg_name_with_similar_value,
applicability = "maybe-incorrect",
code = "{code}"
)]
pub struct FoundWithSimilarValue {
#[primary_span]
pub span: Span,
pub code: String,
}
#[derive(Subdiagnostic)]
#[help_once(lint_unexpected_cfg_name_expected_names)]
pub struct ExpectedNames {
pub possibilities: DiagSymbolList,
pub and_more: usize,
}
#[derive(Subdiagnostic)]
pub enum InvocationHelp {
#[note(lint_unexpected_cfg_doc_cargo)]
Cargo {
#[subdiagnostic]
sub: Option<super::UnexpectedCfgCargoHelp>,
},
#[note(lint_unexpected_cfg_doc_rustc)]
Rustc(#[subdiagnostic] super::UnexpectedCfgRustcHelp),
}
}
#[derive(Subdiagnostic)]
pub struct UnexpectedCfgValueSub {
#[subdiagnostic]
pub code_sugg: unexpected_cfg_value::CodeSuggestion,
#[subdiagnostic]
pub invocation_help: unexpected_cfg_value::InvocationHelp,
}
pub mod unexpected_cfg_value {
use rustc_errors::DiagSymbolList;
use rustc_macros::Subdiagnostic;
use rustc_span::{Span, Symbol};
#[derive(Subdiagnostic)]
pub enum CodeSuggestion {
ChangeValue {
#[subdiagnostic]
expected_values: ExpectedValues,
#[subdiagnostic]
suggestion: Option<ChangeValueSuggestion>,
},
#[note(lint_unexpected_cfg_value_no_expected_value)]
RemoveValue {
#[subdiagnostic]
suggestion: Option<RemoveValueSuggestion>,
name: Symbol,
},
#[note(lint_unexpected_cfg_value_no_expected_values)]
RemoveCondition {
#[subdiagnostic]
suggestion: RemoveConditionSuggestion,
name: Symbol,
},
}
#[derive(Subdiagnostic)]
pub enum ChangeValueSuggestion {
#[suggestion(
lint_unexpected_cfg_value_similar_name,
code = r#""{best_match}""#,
applicability = "maybe-incorrect"
)]
SimilarName {
#[primary_span]
span: Span,
best_match: Symbol,
},
#[suggestion(
lint_unexpected_cfg_value_specify_value,
code = r#" = "{first_possibility}""#,
applicability = "maybe-incorrect"
)]
SpecifyValue {
#[primary_span]
span: Span,
first_possibility: Symbol,
},
}
#[derive(Subdiagnostic)]
#[suggestion(
lint_unexpected_cfg_value_remove_value,
code = "",
applicability = "maybe-incorrect"
)]
pub struct RemoveValueSuggestion {
#[primary_span]
pub span: Span,
}
#[derive(Subdiagnostic)]
#[suggestion(
lint_unexpected_cfg_value_remove_condition,
code = "",
applicability = "maybe-incorrect"
)]
pub struct RemoveConditionSuggestion {
#[primary_span]
pub span: Span,
}
#[derive(Subdiagnostic)]
#[note(lint_unexpected_cfg_value_expected_values)]
pub struct ExpectedValues {
pub name: Symbol,
pub have_none_possibility: bool,
pub possibilities: DiagSymbolList,
pub and_more: usize,
}
#[derive(Subdiagnostic)]
pub enum InvocationHelp {
#[note(lint_unexpected_cfg_doc_cargo)]
Cargo(#[subdiagnostic] Option<CargoHelp>),
#[note(lint_unexpected_cfg_doc_rustc)]
Rustc(#[subdiagnostic] Option<super::UnexpectedCfgRustcHelp>),
}
#[derive(Subdiagnostic)]
pub enum CargoHelp {
#[help(lint_unexpected_cfg_value_add_feature)]
AddFeature {
value: Symbol,
},
#[help(lint_unexpected_cfg_define_features)]
DefineFeatures,
Other(#[subdiagnostic] super::UnexpectedCfgCargoHelp),
}
}