diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index bd86a0be67669..88a02917f368a 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -18,34 +18,32 @@ use self::TargetLint::*; use crate::levels::LintLevelsBuilder; use crate::passes::{EarlyLintPassObject, LateLintPassObject}; -use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync; -use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder, DiagnosticMessage}; -use rustc_errors::{Applicability, DecorateLint, MultiSpan, SuggestionStyle}; +use rustc_errors::{DecorateLint, DiagnosticBuilder, DiagnosticMessage, MultiSpan}; use rustc_feature::Features; use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::def_id::{CrateNum, DefId}; use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; use rustc_middle::middle::privacy::EffectiveVisibilities; -use rustc_middle::middle::stability; use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; use rustc_middle::ty::print::{with_no_trimmed_paths, PrintError}; use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, TyCtxt}; -use rustc_session::config::ExpectedValues; use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId}; use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId}; use rustc_session::{LintStoreMarker, Session}; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::symbol::{sym, Ident, Symbol}; -use rustc_span::{BytePos, Span}; +use rustc_span::Span; use rustc_target::abi; use std::cell::Cell; use std::iter; use std::slice; +mod diagnostics; + type EarlyLintPassFactory = dyn Fn() -> EarlyLintPassObject + sync::DynSend + sync::DynSync; type LateLintPassFactory = dyn for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> + sync::DynSend + sync::DynSync; @@ -531,445 +529,9 @@ pub trait LintContext { diagnostic: BuiltinLintDiagnostics, ) { // We first generate a blank diagnostic. - self.lookup(lint, span, msg,|db| { + self.lookup(lint, span, msg, |db| { // Now, set up surrounding context. - let sess = self.sess(); - match diagnostic { - BuiltinLintDiagnostics::UnicodeTextFlow(span, content) => { - let spans: Vec<_> = content - .char_indices() - .filter_map(|(i, c)| { - TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| { - let lo = span.lo() + BytePos(2 + i as u32); - (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32))) - }) - }) - .collect(); - let (an, s) = match spans.len() { - 1 => ("an ", ""), - _ => ("", "s"), - }; - db.span_label(span, format!( - "this comment contains {an}invisible unicode text flow control codepoint{s}", - )); - for (c, span) in &spans { - db.span_label(*span, format!("{c:?}")); - } - db.note( - "these kind of unicode codepoints change the way text flows on \ - applications that support them, but can cause confusion because they \ - change the order of characters on the screen", - ); - if !spans.is_empty() { - db.multipart_suggestion_with_style( - "if their presence wasn't intentional, you can remove them", - spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(), - Applicability::MachineApplicable, - SuggestionStyle::HideCodeAlways, - ); - } - }, - BuiltinLintDiagnostics::Normal => (), - BuiltinLintDiagnostics::AbsPathWithModule(span) => { - let (sugg, app) = match sess.source_map().span_to_snippet(span) { - Ok(ref s) => { - // FIXME(Manishearth) ideally the emitting code - // can tell us whether or not this is global - let opt_colon = - if s.trim_start().starts_with("::") { "" } else { "::" }; - - (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) - } - Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), - }; - db.span_suggestion(span, "use `crate`", sugg, app); - } - BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => { - db.span_label( - span, - "names from parent modules are not accessible without an explicit import", - ); - } - BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths( - span_def, - ) => { - db.span_note(span_def, "the macro is defined here"); - } - BuiltinLintDiagnostics::ElidedLifetimesInPaths( - n, - path_span, - incl_angl_brckt, - insertion_span, - ) => { - add_elided_lifetime_in_path_suggestion( - sess.source_map(), - db, - n, - path_span, - incl_angl_brckt, - insertion_span, - ); - } - BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => { - db.span_suggestion(span, note, sugg, Applicability::MaybeIncorrect); - } - BuiltinLintDiagnostics::UnusedImports(message, replaces, in_test_module) => { - if !replaces.is_empty() { - db.tool_only_multipart_suggestion( - message, - replaces, - Applicability::MachineApplicable, - ); - } - - if let Some(span) = in_test_module { - db.span_help( - self.sess().source_map().guess_head_span(span), - "consider adding a `#[cfg(test)]` to the containing module", - ); - } - } - BuiltinLintDiagnostics::RedundantImport(spans, ident) => { - for (span, is_imported) in spans { - let introduced = if is_imported { "imported" } else { "defined" }; - db.span_label( - span, - format!("the item `{ident}` is already {introduced} here"), - ); - } - } - BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => { - stability::deprecation_suggestion(db, "macro", suggestion, span) - } - BuiltinLintDiagnostics::UnusedDocComment(span) => { - db.span_label(span, "rustdoc does not generate documentation for macro invocations"); - db.help("to document an item produced by a macro, \ - the macro must produce the documentation as part of its expansion"); - } - BuiltinLintDiagnostics::PatternsInFnsWithoutBody(span, ident) => { - db.span_suggestion(span, "remove `mut` from the parameter", ident, Applicability::MachineApplicable); - } - BuiltinLintDiagnostics::MissingAbi(span, default_abi) => { - db.span_label(span, "ABI should be specified here"); - db.help(format!("the default ABI is {}", default_abi.name())); - } - BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => { - db.span_label(span, "the attribute is introduced here"); - } - BuiltinLintDiagnostics::ProcMacroBackCompat(note) => { - db.note(note); - } - BuiltinLintDiagnostics::OrPatternsBackCompat(span,suggestion) => { - db.span_suggestion(span, "use pat_param to preserve semantics", suggestion, Applicability::MachineApplicable); - } - BuiltinLintDiagnostics::ReservedPrefix(span) => { - db.span_label(span, "unknown prefix"); - db.span_suggestion_verbose( - span.shrink_to_hi(), - "insert whitespace here to avoid this being parsed as a prefix in Rust 2021", - " ", - Applicability::MachineApplicable, - ); - } - BuiltinLintDiagnostics::UnusedBuiltinAttribute { - attr_name, - macro_name, - invoc_span - } => { - db.span_note( - invoc_span, - format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`") - ); - } - BuiltinLintDiagnostics::TrailingMacro(is_trailing, name) => { - if is_trailing { - db.note("macro invocations at the end of a block are treated as expressions"); - db.note(format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`")); - } - } - BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => { - db.multipart_suggestion( - "wrap this expression in parentheses", - vec![(span.shrink_to_lo(), "(".to_string()), - (span.shrink_to_hi(), ")".to_string())], - Applicability::MachineApplicable - ); - } - BuiltinLintDiagnostics::NamedAsmLabel(help) => { - db.help(help); - db.note("see the asm section of Rust By Example for more information"); - }, - BuiltinLintDiagnostics::UnexpectedCfgName((name, name_span), value) => { - let possibilities: Vec = sess.parse_sess.check_config.expecteds.keys().copied().collect(); - let is_from_cargo = std::env::var_os("CARGO").is_some(); - let mut is_feature_cfg = name == sym::feature; - - if is_feature_cfg && is_from_cargo { - db.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.parse_sess.check_config.expecteds.get(&best_match) { - 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)) { - db.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) { - db.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() { - db.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 { - db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", best_match, Applicability::MaybeIncorrect); - }; - } else { - db.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("`, `"); - db.help(format!("expected values for `{best_match}` are: `{possibilities}`")); - } - } else { - db.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 !possibilities.is_empty() { - let mut possibilities = possibilities.iter() - .map(Symbol::as_str) - .collect::>(); - possibilities.sort(); - let possibilities = possibilities.join("`, `"); - - // The list of expected names can be long (even by default) and - // so the diagnostic produced can take a lot of space. To avoid - // cloging the user output we only want to print that diagnostic - // once. - db.help_once(format!("expected names are: `{possibilities}`")); - } - - 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 { - db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); - } - db.note("see for more information about checking conditional configuration"); - } else { - db.help(format!("to expect this configuration use `--check-cfg={inst}`")); - db.note("see for more information about checking conditional configuration"); - } - }, - BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => { - let Some(ExpectedValues::Some(values)) = &sess.parse_sess.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; - let possibilities: Vec = values.iter() - .inspect(|a| have_none_possibility |= a.is_none()) - .copied() - .flatten() - .collect(); - let is_from_cargo = std::env::var_os("CARGO").is_some(); - - // 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() { - { - let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::>(); - possibilities.sort(); - - let possibilities = possibilities.join("`, `"); - let none = if have_none_possibility { "(none), " } else { "" }; - - db.note(format!("expected values for `{name}` are: {none}`{possibilities}`")); - } - - 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) { - db.span_suggestion(value_span, "there is a expected value with a similar name", format!("\"{best_match}\""), Applicability::MaybeIncorrect); - - } - } else if let &[first_possibility] = &possibilities[..] { - db.span_suggestion(name_span.shrink_to_hi(), "specify a config value", format!(" = \"{first_possibility}\""), Applicability::MaybeIncorrect); - } - } else if have_none_possibility { - db.note(format!("no expected value for `{name}`")); - if let Some((_value, value_span)) = value { - db.span_suggestion(name_span.shrink_to_hi().to(value_span), "remove the value", "", Applicability::MaybeIncorrect); - } - } - - 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 { - db.help(format!("consider adding `{value}` as a feature in `Cargo.toml`")); - } - } else { - db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); - } - db.note("see for more information about checking conditional configuration"); - } else { - db.help(format!("to expect this configuration use `--check-cfg={inst}`")); - db.note("see for more information about checking conditional configuration"); - } - }, - BuiltinLintDiagnostics::DeprecatedWhereclauseLocation(new_span, suggestion) => { - db.multipart_suggestion( - "move it to the end of the type declaration", - vec![(db.span.primary_span().unwrap(), "".to_string()), (new_span, suggestion)], - Applicability::MachineApplicable, - ); - db.note( - "see issue #89122 for more information", - ); - }, - BuiltinLintDiagnostics::SingleUseLifetime { - param_span, - use_span: Some((use_span, elide)), - deletion_span, - } => { - debug!(?param_span, ?use_span, ?deletion_span); - db.span_label(param_span, "this lifetime..."); - db.span_label(use_span, "...is used only here"); - if let Some(deletion_span) = deletion_span { - let msg = "elide the single-use lifetime"; - let (use_span, replace_lt) = if elide { - let use_span = sess.source_map().span_extend_while( - use_span, - char::is_whitespace, - ).unwrap_or(use_span); - (use_span, String::new()) - } else { - (use_span, "'_".to_owned()) - }; - debug!(?deletion_span, ?use_span); - - // issue 107998 for the case such as a wrong function pointer type - // `deletion_span` is empty and there is no need to report lifetime uses here - let suggestions = if deletion_span.is_empty() { - vec![(use_span, replace_lt)] - } else { - vec![(deletion_span, String::new()), (use_span, replace_lt)] - }; - db.multipart_suggestion( - msg, - suggestions, - Applicability::MachineApplicable, - ); - } - }, - BuiltinLintDiagnostics::SingleUseLifetime { - param_span: _, - use_span: None, - deletion_span, - } => { - debug!(?deletion_span); - if let Some(deletion_span) = deletion_span { - db.span_suggestion( - deletion_span, - "elide the unused lifetime", - "", - Applicability::MachineApplicable, - ); - } - }, - BuiltinLintDiagnostics::NamedArgumentUsedPositionally{ position_sp_to_replace, position_sp_for_msg, named_arg_sp, named_arg_name, is_formatting_arg} => { - db.span_label(named_arg_sp, "this named argument is referred to by position in formatting string"); - if let Some(positional_arg_for_msg) = position_sp_for_msg { - let msg = format!("this formatting argument uses named argument `{named_arg_name}` by position"); - db.span_label(positional_arg_for_msg, msg); - } - - if let Some(positional_arg_to_replace) = position_sp_to_replace { - let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name }; - let span_to_replace = if let Ok(positional_arg_content) = - self.sess().source_map().span_to_snippet(positional_arg_to_replace) && positional_arg_content.starts_with(':') { - positional_arg_to_replace.shrink_to_lo() - } else { - positional_arg_to_replace - }; - db.span_suggestion_verbose( - span_to_replace, - "use the named argument by name to avoid ambiguity", - name, - Applicability::MaybeIncorrect, - ); - } - } - BuiltinLintDiagnostics::ByteSliceInPackedStructWithDerive => { - db.help("consider implementing the trait by hand, or remove the `packed` attribute"); - } - BuiltinLintDiagnostics::UnusedExternCrate { removal_span }=> { - db.span_suggestion( - removal_span, - "remove it", - "", - Applicability::MachineApplicable, - ); - } - BuiltinLintDiagnostics::ExternCrateNotIdiomatic { vis_span, ident_span }=> { - let suggestion_span = vis_span.between(ident_span); - db.span_suggestion_verbose( - suggestion_span, - "convert it to a `use`", - if vis_span.is_empty() { "use " } else { " use " }, - Applicability::MachineApplicable, - ); - } - BuiltinLintDiagnostics::AmbiguousGlobImports { diag } => { - rustc_errors::report_ambiguity_error(db, diag); - } - BuiltinLintDiagnostics::AmbiguousGlobReexports { name, namespace, first_reexport_span, duplicate_reexport_span } => { - db.span_label(first_reexport_span, format!("the name `{name}` in the {namespace} namespace is first re-exported here")); - db.span_label(duplicate_reexport_span, format!("but the name `{name}` in the {namespace} namespace is also re-exported here")); - } - BuiltinLintDiagnostics::HiddenGlobReexports { name, namespace, glob_reexport_span, private_item_span } => { - db.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here")); - db.span_note(private_item_span, "but the private item here shadows it".to_owned()); - } - BuiltinLintDiagnostics::UnusedQualifications { removal_span } => { - db.span_suggestion_verbose( - removal_span, - "remove the unnecessary path segments", - "", - Applicability::MachineApplicable - ); - } - BuiltinLintDiagnostics::AssociatedConstElidedLifetime { elided, span } => { - db.span_suggestion_verbose( - if elided { span.shrink_to_hi() } else { span }, - "use the `'static` lifetime", - if elided { "'static " } else { "'static" }, - Applicability::MachineApplicable - ); - }, - BuiltinLintDiagnostics::RedundantImportVisibility { max_vis, span } => { - db.span_note(span, format!("the most public imported item is `{max_vis}`")); - db.help("reduce the glob import's visibility or increase visibility of imported items"); - } - } + diagnostics::builtin(self.sess(), diagnostic, db); // Rewrap `db`, and pass control to the user. decorate(db) }); diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs new file mode 100644 index 0000000000000..cfb0dff31f619 --- /dev/null +++ b/compiler/rustc_lint/src/context/diagnostics.rs @@ -0,0 +1,532 @@ +use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS; +use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder}; +use rustc_errors::{Applicability, SuggestionStyle}; +use rustc_middle::middle::stability; +use rustc_session::config::ExpectedValues; +use rustc_session::lint::BuiltinLintDiagnostics; +use rustc_session::Session; +use rustc_span::edit_distance::find_best_match_for_name; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::BytePos; + +pub(super) fn builtin( + sess: &Session, + diagnostic: BuiltinLintDiagnostics, + db: &mut DiagnosticBuilder<'_, ()>, +) { + match diagnostic { + BuiltinLintDiagnostics::UnicodeTextFlow(span, content) => { + let spans: Vec<_> = content + .char_indices() + .filter_map(|(i, c)| { + TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| { + let lo = span.lo() + BytePos(2 + i as u32); + (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32))) + }) + }) + .collect(); + let (an, s) = match spans.len() { + 1 => ("an ", ""), + _ => ("", "s"), + }; + db.span_label( + span, + format!( + "this comment contains {an}invisible unicode text flow control codepoint{s}", + ), + ); + for (c, span) in &spans { + db.span_label(*span, format!("{c:?}")); + } + db.note( + "these kind of unicode codepoints change the way text flows on \ + applications that support them, but can cause confusion because they \ + change the order of characters on the screen", + ); + if !spans.is_empty() { + db.multipart_suggestion_with_style( + "if their presence wasn't intentional, you can remove them", + spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(), + Applicability::MachineApplicable, + SuggestionStyle::HideCodeAlways, + ); + } + } + BuiltinLintDiagnostics::Normal => (), + BuiltinLintDiagnostics::AbsPathWithModule(span) => { + let (sugg, app) = match sess.source_map().span_to_snippet(span) { + Ok(ref s) => { + // FIXME(Manishearth) ideally the emitting code + // can tell us whether or not this is global + let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; + + (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) + } + Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), + }; + db.span_suggestion(span, "use `crate`", sugg, app); + } + BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => { + db.span_label( + span, + "names from parent modules are not accessible without an explicit import", + ); + } + BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def) => { + db.span_note(span_def, "the macro is defined here"); + } + BuiltinLintDiagnostics::ElidedLifetimesInPaths( + n, + path_span, + incl_angl_brckt, + insertion_span, + ) => { + add_elided_lifetime_in_path_suggestion( + sess.source_map(), + db, + n, + path_span, + incl_angl_brckt, + insertion_span, + ); + } + BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => { + db.span_suggestion(span, note, sugg, Applicability::MaybeIncorrect); + } + BuiltinLintDiagnostics::UnusedImports(message, replaces, in_test_module) => { + if !replaces.is_empty() { + db.tool_only_multipart_suggestion( + message, + replaces, + Applicability::MachineApplicable, + ); + } + + if let Some(span) = in_test_module { + db.span_help( + sess.source_map().guess_head_span(span), + "consider adding a `#[cfg(test)]` to the containing module", + ); + } + } + BuiltinLintDiagnostics::RedundantImport(spans, ident) => { + for (span, is_imported) in spans { + let introduced = if is_imported { "imported" } else { "defined" }; + db.span_label(span, format!("the item `{ident}` is already {introduced} here")); + } + } + BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => { + stability::deprecation_suggestion(db, "macro", suggestion, span) + } + BuiltinLintDiagnostics::UnusedDocComment(span) => { + db.span_label(span, "rustdoc does not generate documentation for macro invocations"); + db.help("to document an item produced by a macro, \ + the macro must produce the documentation as part of its expansion"); + } + BuiltinLintDiagnostics::PatternsInFnsWithoutBody(span, ident) => { + db.span_suggestion( + span, + "remove `mut` from the parameter", + ident, + Applicability::MachineApplicable, + ); + } + BuiltinLintDiagnostics::MissingAbi(span, default_abi) => { + db.span_label(span, "ABI should be specified here"); + db.help(format!("the default ABI is {}", default_abi.name())); + } + BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => { + db.span_label(span, "the attribute is introduced here"); + } + BuiltinLintDiagnostics::ProcMacroBackCompat(note) => { + db.note(note); + } + BuiltinLintDiagnostics::OrPatternsBackCompat(span, suggestion) => { + db.span_suggestion( + span, + "use pat_param to preserve semantics", + suggestion, + Applicability::MachineApplicable, + ); + } + BuiltinLintDiagnostics::ReservedPrefix(span) => { + db.span_label(span, "unknown prefix"); + db.span_suggestion_verbose( + span.shrink_to_hi(), + "insert whitespace here to avoid this being parsed as a prefix in Rust 2021", + " ", + Applicability::MachineApplicable, + ); + } + BuiltinLintDiagnostics::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => { + db.span_note( + invoc_span, + format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`") + ); + } + BuiltinLintDiagnostics::TrailingMacro(is_trailing, name) => { + if is_trailing { + db.note("macro invocations at the end of a block are treated as expressions"); + db.note(format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`")); + } + } + BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => { + db.multipart_suggestion( + "wrap this expression in parentheses", + vec![ + (span.shrink_to_lo(), "(".to_string()), + (span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MachineApplicable, + ); + } + BuiltinLintDiagnostics::NamedAsmLabel(help) => { + db.help(help); + db.note("see the asm section of Rust By Example for more information"); + } + BuiltinLintDiagnostics::UnexpectedCfgName((name, name_span), value) => { + let possibilities: Vec = + sess.parse_sess.check_config.expecteds.keys().copied().collect(); + let is_from_cargo = std::env::var_os("CARGO").is_some(); + let mut is_feature_cfg = name == sym::feature; + + if is_feature_cfg && is_from_cargo { + db.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.parse_sess.check_config.expecteds.get(&best_match) + { + 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)) { + db.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) { + db.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() { + db.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 { + db.span_suggestion( + name_span.to(value_span), + "there is a config with a similar name and different values", + best_match, + Applicability::MaybeIncorrect, + ); + }; + } else { + db.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("`, `"); + db.help(format!( + "expected values for `{best_match}` are: `{possibilities}`" + )); + } + } else { + db.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 !possibilities.is_empty() { + let mut possibilities = + possibilities.iter().map(Symbol::as_str).collect::>(); + possibilities.sort(); + let possibilities = possibilities.join("`, `"); + + // The list of expected names can be long (even by default) and + // so the diagnostic produced can take a lot of space. To avoid + // cloging the user output we only want to print that diagnostic + // once. + db.help_once(format!("expected names are: `{possibilities}`")); + } + + 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 { + db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); + } + db.note("see for more information about checking conditional configuration"); + } else { + db.help(format!("to expect this configuration use `--check-cfg={inst}`")); + db.note("see for more information about checking conditional configuration"); + } + } + BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => { + let Some(ExpectedValues::Some(values)) = + &sess.parse_sess.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; + let possibilities: Vec = values + .iter() + .inspect(|a| have_none_possibility |= a.is_none()) + .copied() + .flatten() + .collect(); + let is_from_cargo = std::env::var_os("CARGO").is_some(); + + // 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() { + { + let mut possibilities = + possibilities.iter().map(Symbol::as_str).collect::>(); + possibilities.sort(); + + let possibilities = possibilities.join("`, `"); + let none = if have_none_possibility { "(none), " } else { "" }; + + db.note(format!("expected values for `{name}` are: {none}`{possibilities}`")); + } + + 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) + { + db.span_suggestion( + value_span, + "there is a expected value with a similar name", + format!("\"{best_match}\""), + Applicability::MaybeIncorrect, + ); + } + } else if let &[first_possibility] = &possibilities[..] { + db.span_suggestion( + name_span.shrink_to_hi(), + "specify a config value", + format!(" = \"{first_possibility}\""), + Applicability::MaybeIncorrect, + ); + } + } else if have_none_possibility { + db.note(format!("no expected value for `{name}`")); + if let Some((_value, value_span)) = value { + db.span_suggestion( + name_span.shrink_to_hi().to(value_span), + "remove the value", + "", + Applicability::MaybeIncorrect, + ); + } + } + + 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 { + db.help(format!("consider adding `{value}` as a feature in `Cargo.toml`")); + } + } else { + db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); + } + db.note("see for more information about checking conditional configuration"); + } else { + db.help(format!("to expect this configuration use `--check-cfg={inst}`")); + db.note("see for more information about checking conditional configuration"); + } + } + BuiltinLintDiagnostics::DeprecatedWhereclauseLocation(new_span, suggestion) => { + db.multipart_suggestion( + "move it to the end of the type declaration", + vec![(db.span.primary_span().unwrap(), "".to_string()), (new_span, suggestion)], + Applicability::MachineApplicable, + ); + db.note( + "see issue #89122 for more information", + ); + } + BuiltinLintDiagnostics::SingleUseLifetime { + param_span, + use_span: Some((use_span, elide)), + deletion_span, + } => { + debug!(?param_span, ?use_span, ?deletion_span); + db.span_label(param_span, "this lifetime..."); + db.span_label(use_span, "...is used only here"); + if let Some(deletion_span) = deletion_span { + let msg = "elide the single-use lifetime"; + let (use_span, replace_lt) = if elide { + let use_span = sess + .source_map() + .span_extend_while(use_span, char::is_whitespace) + .unwrap_or(use_span); + (use_span, String::new()) + } else { + (use_span, "'_".to_owned()) + }; + debug!(?deletion_span, ?use_span); + + // issue 107998 for the case such as a wrong function pointer type + // `deletion_span` is empty and there is no need to report lifetime uses here + let suggestions = if deletion_span.is_empty() { + vec![(use_span, replace_lt)] + } else { + vec![(deletion_span, String::new()), (use_span, replace_lt)] + }; + db.multipart_suggestion(msg, suggestions, Applicability::MachineApplicable); + } + } + BuiltinLintDiagnostics::SingleUseLifetime { + param_span: _, + use_span: None, + deletion_span, + } => { + debug!(?deletion_span); + if let Some(deletion_span) = deletion_span { + db.span_suggestion( + deletion_span, + "elide the unused lifetime", + "", + Applicability::MachineApplicable, + ); + } + } + BuiltinLintDiagnostics::NamedArgumentUsedPositionally { + position_sp_to_replace, + position_sp_for_msg, + named_arg_sp, + named_arg_name, + is_formatting_arg, + } => { + db.span_label( + named_arg_sp, + "this named argument is referred to by position in formatting string", + ); + if let Some(positional_arg_for_msg) = position_sp_for_msg { + let msg = format!( + "this formatting argument uses named argument `{named_arg_name}` by position" + ); + db.span_label(positional_arg_for_msg, msg); + } + + if let Some(positional_arg_to_replace) = position_sp_to_replace { + let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name }; + let span_to_replace = if let Ok(positional_arg_content) = + sess.source_map().span_to_snippet(positional_arg_to_replace) + && positional_arg_content.starts_with(':') + { + positional_arg_to_replace.shrink_to_lo() + } else { + positional_arg_to_replace + }; + db.span_suggestion_verbose( + span_to_replace, + "use the named argument by name to avoid ambiguity", + name, + Applicability::MaybeIncorrect, + ); + } + } + BuiltinLintDiagnostics::ByteSliceInPackedStructWithDerive => { + db.help("consider implementing the trait by hand, or remove the `packed` attribute"); + } + BuiltinLintDiagnostics::UnusedExternCrate { removal_span } => { + db.span_suggestion(removal_span, "remove it", "", Applicability::MachineApplicable); + } + BuiltinLintDiagnostics::ExternCrateNotIdiomatic { vis_span, ident_span } => { + let suggestion_span = vis_span.between(ident_span); + db.span_suggestion_verbose( + suggestion_span, + "convert it to a `use`", + if vis_span.is_empty() { "use " } else { " use " }, + Applicability::MachineApplicable, + ); + } + BuiltinLintDiagnostics::AmbiguousGlobImports { diag } => { + rustc_errors::report_ambiguity_error(db, diag); + } + BuiltinLintDiagnostics::AmbiguousGlobReexports { + name, + namespace, + first_reexport_span, + duplicate_reexport_span, + } => { + db.span_label( + first_reexport_span, + format!("the name `{name}` in the {namespace} namespace is first re-exported here"), + ); + db.span_label( + duplicate_reexport_span, + format!( + "but the name `{name}` in the {namespace} namespace is also re-exported here" + ), + ); + } + BuiltinLintDiagnostics::HiddenGlobReexports { + name, + namespace, + glob_reexport_span, + private_item_span, + } => { + db.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here")); + db.span_note(private_item_span, "but the private item here shadows it".to_owned()); + } + BuiltinLintDiagnostics::UnusedQualifications { removal_span } => { + db.span_suggestion_verbose( + removal_span, + "remove the unnecessary path segments", + "", + Applicability::MachineApplicable, + ); + } + BuiltinLintDiagnostics::AssociatedConstElidedLifetime { elided, span } => { + db.span_suggestion_verbose( + if elided { span.shrink_to_hi() } else { span }, + "use the `'static` lifetime", + if elided { "'static " } else { "'static" }, + Applicability::MachineApplicable, + ); + } + BuiltinLintDiagnostics::RedundantImportVisibility { max_vis, span } => { + db.span_note(span, format!("the most public imported item is `{max_vis}`")); + db.help("reduce the glob import's visibility or increase visibility of imported items"); + } + } +}