diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 0b6b9899f48a6..b4a22a545f04f 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -3667,7 +3667,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } /// Gets the `#[diagnostic::on_unknown]` attribute data associated with this `DefId`. - fn on_unknown_data(&self, def_id: DefId) -> Option<&Directive> { + pub(crate) fn on_unknown_data(&self, def_id: DefId) -> Option<&Directive> { match def_id.as_local() { Some(local) => Some(self.on_unknown_data.get(&local)?.directive.as_ref()), None => find_attr!(self.tcx, def_id, OnUnknown{ directive } => directive)?.as_deref(), diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 5260f724385f8..e26bfa6b96d51 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -19,6 +19,7 @@ use rustc_errors::{ struct_span_code_err, }; use rustc_hir as hir; +use rustc_hir::attrs::diagnostic::{CustomDiagnostic, FormatArgs}; use rustc_hir::def::Namespace::{self, *}; use rustc_hir::def::{CtorKind, CtorOf, DefKind, MacroKinds}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; @@ -163,6 +164,7 @@ struct BaseError { could_be_expr: bool, suggestion: Option<(Span, &'static str, String)>, module: Option, + notes: Vec, } #[derive(Debug)] @@ -388,7 +390,6 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { // Make the base error. let mut expected = source.descr_expected(); let path_str = Segment::names_to_string(path); - let item_str = path.last().unwrap().ident; if let Some(res) = res { BaseError { @@ -404,12 +405,13 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { could_be_expr, suggestion: None, module: None, + notes: Vec::new(), } } else { let mut span_label = None; let item_ident = path.last().unwrap().ident; let item_span = item_ident.span; - let (mod_prefix, mod_str, module, suggestion) = if path.len() == 1 { + let (tick, mod_prefix, mod_str, module, suggestion) = if path.len() == 1 { debug!(?self.diag_metadata.current_impl_items); debug!(?self.diag_metadata.current_function); let suggestion = if self.current_trait_ref.is_none() @@ -420,7 +422,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { && let Some(item) = items.iter().find(|i| { i.kind.ident().is_some_and(|ident| { // Don't suggest if the item is in Fn signature arguments (#112590). - ident.name == item_str.name && !sig.span.contains(item_span) + ident.name == item_ident.name && !sig.span.contains(item_span) }) }) { let sp = item_span.shrink_to_lo(); @@ -490,15 +492,16 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } else { None }; - (String::new(), "this scope".to_string(), None, suggestion) + ("", String::new(), "this scope".to_string(), None, suggestion) } else if path.len() == 2 && path[0].ident.name == kw::PathRoot { if self.r.tcx.sess.edition() > Edition::Edition2015 { // In edition 2018 onwards, the `::foo` syntax may only pull from the extern prelude // which overrides all other expectations of item type expected = "crate"; - (String::new(), "the list of imported crates".to_string(), None, None) + ("", String::new(), "the list of imported crates".to_string(), None, None) } else { ( + "", String::new(), "the crate root".to_string(), Some(CRATE_DEF_ID.to_def_id()), @@ -506,7 +509,13 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { ) } } else if path.len() == 2 && path[0].ident.name == kw::Crate { - (String::new(), "the crate root".to_string(), Some(CRATE_DEF_ID.to_def_id()), None) + ( + "", + String::new(), + "the crate root".to_string(), + Some(CRATE_DEF_ID.to_def_id()), + None, + ) } else { let mod_path = &path[..path.len() - 1]; let mod_res = self.resolve_path(mod_path, Some(TypeNS), None, source); @@ -519,41 +528,66 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let mod_prefix = mod_prefix.map_or_else(String::new, |res| format!("{} ", res.descr())); - (mod_prefix, format!("`{}`", Segment::names_to_string(mod_path)), module_did, None) + ("`", mod_prefix, Segment::names_to_string(mod_path), module_did, None) }; - let (fallback_label, suggestion) = if path_str == "async" - && expected.starts_with("struct") - { - ("`async` blocks are only allowed in Rust 2018 or later".to_string(), suggestion) + let suggestion = + if ["true", "false"].contains(&item_ident.to_string().to_lowercase().as_str()) { + // check if we are in situation of typo like `True` instead of `true`. + let item_typo = item_ident.to_string().to_lowercase(); + Some((item_span, "you may want to use a bool value instead", item_typo)) + // FIXME(vincenzopalazzo): make the check smarter, + // and maybe expand with levenshtein distance checks + } else if item_ident.as_str() == "printf" { + Some(( + item_span, + "you may have meant to use the `print` macro", + "print!".to_owned(), + )) + } else { + suggestion + }; + let mut msg = format!( + "cannot find {expected} `{item_ident}` in {mod_prefix}{tick}{mod_str}{tick}" + ); + let mut fallback_label = if path_str == "async" && expected.starts_with("struct") { + "`async` blocks are only allowed in Rust 2018 or later".to_string() } else { - // check if we are in situation of typo like `True` instead of `true`. - let override_suggestion = - if ["true", "false"].contains(&item_str.to_string().to_lowercase().as_str()) { - let item_typo = item_str.to_string().to_lowercase(); - Some((item_span, "you may want to use a bool value instead", item_typo)) - // FIXME(vincenzopalazzo): make the check smarter, - // and maybe expand with levenshtein distance checks - } else if item_str.as_str() == "printf" { - Some(( - item_span, - "you may have meant to use the `print` macro", - "print!".to_owned(), - )) - } else { - suggestion - }; - (format!("not found in {mod_str}"), override_suggestion) + format!("not found in {tick}{mod_str}{tick}") }; + let mut notes = Vec::new(); + if let Some(module_def_id) = module + && let Some(directive) = self.r.on_unknown_data(module_def_id) + { + let args = FormatArgs { unresolved: item_ident.to_string(), this: mod_str, .. }; + let CustomDiagnostic { + message, + label, + notes: custom_notes, + parent_label: _unreachable, + } = directive.eval(None, &args); + if let Some(message) = message { + notes.push(msg); + msg = message; + } + if let Some(label) = label { + fallback_label = label; + if let Some((_, span_label)) = span_label.take() { + notes.push(span_label.to_string()); + } + } + notes.extend(custom_notes); + } BaseError { - msg: format!("cannot find {expected} `{item_str}` in {mod_prefix}{mod_str}"), + msg, fallback_label, span: item_span, span_label, could_be_expr, suggestion, module, + notes, } } } @@ -679,6 +713,9 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let Some((span, label)) = base_error.span_label { err.span_label(span, label); } + for note in &base_error.notes { + err.note(note.clone()); + } if let Some(ref sugg) = base_error.suggestion { err.span_suggestion_verbose(sugg.0, sugg.1, &sugg.2, Applicability::MaybeIncorrect); diff --git a/tests/ui/diagnostic_namespace/on_unknown/late_res.rs b/tests/ui/diagnostic_namespace/on_unknown/late_res.rs new file mode 100644 index 0000000000000..e549cb97b9d3a --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/late_res.rs @@ -0,0 +1,27 @@ +#![crate_type = "lib"] +#![feature(diagnostic_on_unknown)] + +#[diagnostic::on_unknown( + message = "it works `{This}` `{Unresolved}`", + label = "label it works", + note = "note it works" +)] +pub mod empty {} + +fn stuff(x: u32) { + match x { + empty::blah => {} + //~^ ERROR it works `empty` `blah` [E0531] + _ => {} + } + + println!("{}", empty::blah); + //~^ ERROR it works `empty` `blah` [E0425] + + let x = [ + empty::blah, + //~^ ERROR it works `empty` `blah` [E0425] + empty::blah2, + //~^ ERROR it works `empty` `blah2` [E0425] + ]; +} diff --git a/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr b/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr new file mode 100644 index 0000000000000..e2bb621814d47 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr @@ -0,0 +1,40 @@ +error[E0531]: it works `empty` `blah` + --> $DIR/late_res.rs:13:16 + | +LL | empty::blah => {} + | ^^^^ label it works + | + = note: cannot find unit struct, unit variant or constant `blah` in module `empty` + = note: note it works + +error[E0425]: it works `empty` `blah` + --> $DIR/late_res.rs:18:27 + | +LL | println!("{}", empty::blah); + | ^^^^ label it works + | + = note: cannot find value `blah` in module `empty` + = note: note it works + +error[E0425]: it works `empty` `blah` + --> $DIR/late_res.rs:22:16 + | +LL | empty::blah, + | ^^^^ label it works + | + = note: cannot find value `blah` in module `empty` + = note: note it works + +error[E0425]: it works `empty` `blah2` + --> $DIR/late_res.rs:24:16 + | +LL | empty::blah2, + | ^^^^^ label it works + | + = note: cannot find value `blah2` in module `empty` + = note: note it works + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0425, E0531. +For more information about an error, try `rustc --explain E0425`.