From d403761e52e3d2b7d405317bf2c2714b34aba0ae Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:40:00 +0200 Subject: [PATCH 1/5] Add late res on_unknown test --- .../on_unknown/late_res.rs | 24 ++++++++++++++++ .../on_unknown/late_res.stderr | 28 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 tests/ui/diagnostic_namespace/on_unknown/late_res.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown/late_res.stderr 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..969d51f32b2b4 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/late_res.rs @@ -0,0 +1,24 @@ +#![crate_type = "lib"] +#![feature(diagnostic_on_unknown)] + +#[diagnostic::on_unknown(message = "it works")] +pub mod empty {} + +fn stuff(x: u32) { + match x { + empty::blah => {} + //~^ ERROR cannot find unit struct, unit variant or constant `blah` in module `empty` [E0531] + _ => {} + } + + println!("{}", empty::blah); + //~^ ERROR cannot find value `blah` in module `empty` [E0425] + + let x = [ + empty::blah, + //~^ ERROR cannot find value `blah` in module `empty` [E0425] + + empty::blah2, + //~^ ERROR cannot find value `blah2` in module `empty` [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..58d6d7987afd6 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr @@ -0,0 +1,28 @@ +error[E0531]: cannot find unit struct, unit variant or constant `blah` in module `empty` + --> $DIR/late_res.rs:9:16 + | +LL | empty::blah => {} + | ^^^^ not found in `empty` + +error[E0425]: cannot find value `blah` in module `empty` + --> $DIR/late_res.rs:14:27 + | +LL | println!("{}", empty::blah); + | ^^^^ not found in `empty` + +error[E0425]: cannot find value `blah` in module `empty` + --> $DIR/late_res.rs:18:16 + | +LL | empty::blah, + | ^^^^ not found in `empty` + +error[E0425]: cannot find value `blah2` in module `empty` + --> $DIR/late_res.rs:21:16 + | +LL | empty::blah2, + | ^^^^^ not found in `empty` + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0425, E0531. +For more information about an error, try `rustc --explain E0425`. From 023d55ef2ba5dae6457a6129d0898ebae1538ab3 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:40:05 +0200 Subject: [PATCH 2/5] Factor out async rust 2018+ suggestion --- .../rustc_resolve/src/late/diagnostics.rs | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 5260f724385f8..dfb172e495927 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -522,33 +522,30 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { (mod_prefix, format!("`{}`", 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) - } 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) - }; + let suggestion = + if ["true", "false"].contains(&item_str.to_string().to_lowercase().as_str()) { + // check if we are in situation of typo like `True` instead of `true`. + 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 + }; BaseError { msg: format!("cannot find {expected} `{item_str}` in {mod_prefix}{mod_str}"), - fallback_label, + fallback_label: if path_str == "async" && expected.starts_with("struct") { + "`async` blocks are only allowed in Rust 2018 or later".to_string() + } else { + format!("not found in {mod_str}") + }, span: item_span, span_label, could_be_expr, From 80b6fdba2cf3c61e1e8fed3d2cf7b94a2292b1f2 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:05:57 +0200 Subject: [PATCH 3/5] Plug diagnostic::on_unknown into late resolution --- .../rustc_resolve/src/diagnostics/impls.rs | 2 +- .../rustc_resolve/src/late/diagnostics.rs | 62 +++++++++++++++---- .../on_unknown/late_res.rs | 8 +-- .../on_unknown/late_res.stderr | 16 +++-- 4 files changed, 68 insertions(+), 20 deletions(-) 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 dfb172e495927..6f4388ff05abe 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)] @@ -404,12 +406,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() @@ -490,15 +493,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 +510,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,7 +529,7 @@ 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 suggestion = @@ -538,19 +548,46 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } else { suggestion }; + let mut msg = + format!("cannot find {expected} `{item_str}` 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 { + 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_str.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}"), - fallback_label: if path_str == "async" && expected.starts_with("struct") { - "`async` blocks are only allowed in Rust 2018 or later".to_string() - } else { - format!("not found in {mod_str}") - }, + msg, + fallback_label, span: item_span, span_label, could_be_expr, suggestion, module, + notes, } } } @@ -676,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 index 969d51f32b2b4..93f84dc8fb63d 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/late_res.rs +++ b/tests/ui/diagnostic_namespace/on_unknown/late_res.rs @@ -7,18 +7,18 @@ pub mod empty {} fn stuff(x: u32) { match x { empty::blah => {} - //~^ ERROR cannot find unit struct, unit variant or constant `blah` in module `empty` [E0531] + //~^ ERROR it works [E0531] _ => {} } println!("{}", empty::blah); - //~^ ERROR cannot find value `blah` in module `empty` [E0425] + //~^ ERROR it works [E0425] let x = [ empty::blah, - //~^ ERROR cannot find value `blah` in module `empty` [E0425] + //~^ ERROR it works [E0425] empty::blah2, - //~^ ERROR cannot find value `blah2` in module `empty` [E0425] + //~^ ERROR it works [E0425] ]; } diff --git a/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr b/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr index 58d6d7987afd6..e65831b489bf5 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr +++ b/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr @@ -1,26 +1,34 @@ -error[E0531]: cannot find unit struct, unit variant or constant `blah` in module `empty` +error[E0531]: it works --> $DIR/late_res.rs:9:16 | LL | empty::blah => {} | ^^^^ not found in `empty` + | + = note: cannot find unit struct, unit variant or constant `blah` in module `empty` -error[E0425]: cannot find value `blah` in module `empty` +error[E0425]: it works --> $DIR/late_res.rs:14:27 | LL | println!("{}", empty::blah); | ^^^^ not found in `empty` + | + = note: cannot find value `blah` in module `empty` -error[E0425]: cannot find value `blah` in module `empty` +error[E0425]: it works --> $DIR/late_res.rs:18:16 | LL | empty::blah, | ^^^^ not found in `empty` + | + = note: cannot find value `blah` in module `empty` -error[E0425]: cannot find value `blah2` in module `empty` +error[E0425]: it works --> $DIR/late_res.rs:21:16 | LL | empty::blah2, | ^^^^^ not found in `empty` + | + = note: cannot find value `blah2` in module `empty` error: aborting due to 4 previous errors From 4a1b9dd7f52d290b511807e235dc299de3d863e0 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:06:45 +0200 Subject: [PATCH 4/5] Update late_res test --- .../on_unknown/late_res.rs | 15 ++++++---- .../on_unknown/late_res.stderr | 28 +++++++++++-------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/tests/ui/diagnostic_namespace/on_unknown/late_res.rs b/tests/ui/diagnostic_namespace/on_unknown/late_res.rs index 93f84dc8fb63d..e549cb97b9d3a 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/late_res.rs +++ b/tests/ui/diagnostic_namespace/on_unknown/late_res.rs @@ -1,24 +1,27 @@ #![crate_type = "lib"] #![feature(diagnostic_on_unknown)] -#[diagnostic::on_unknown(message = "it works")] +#[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 [E0531] + //~^ ERROR it works `empty` `blah` [E0531] _ => {} } println!("{}", empty::blah); - //~^ ERROR it works [E0425] + //~^ ERROR it works `empty` `blah` [E0425] let x = [ empty::blah, - //~^ ERROR it works [E0425] - + //~^ ERROR it works `empty` `blah` [E0425] empty::blah2, - //~^ ERROR it works [E0425] + //~^ 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 index e65831b489bf5..e2bb621814d47 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr +++ b/tests/ui/diagnostic_namespace/on_unknown/late_res.stderr @@ -1,34 +1,38 @@ -error[E0531]: it works - --> $DIR/late_res.rs:9:16 +error[E0531]: it works `empty` `blah` + --> $DIR/late_res.rs:13:16 | LL | empty::blah => {} - | ^^^^ not found in `empty` + | ^^^^ label it works | = note: cannot find unit struct, unit variant or constant `blah` in module `empty` + = note: note it works -error[E0425]: it works - --> $DIR/late_res.rs:14:27 +error[E0425]: it works `empty` `blah` + --> $DIR/late_res.rs:18:27 | LL | println!("{}", empty::blah); - | ^^^^ not found in `empty` + | ^^^^ label it works | = note: cannot find value `blah` in module `empty` + = note: note it works -error[E0425]: it works - --> $DIR/late_res.rs:18:16 +error[E0425]: it works `empty` `blah` + --> $DIR/late_res.rs:22:16 | LL | empty::blah, - | ^^^^ not found in `empty` + | ^^^^ label it works | = note: cannot find value `blah` in module `empty` + = note: note it works -error[E0425]: it works - --> $DIR/late_res.rs:21:16 +error[E0425]: it works `empty` `blah2` + --> $DIR/late_res.rs:24:16 | LL | empty::blah2, - | ^^^^^ not found in `empty` + | ^^^^^ label it works | = note: cannot find value `blah2` in module `empty` + = note: note it works error: aborting due to 4 previous errors From bf4f8c17135e353fca086b873ed269a4ac77fb30 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:27:15 +0200 Subject: [PATCH 5/5] remove duplicate local --- compiler/rustc_resolve/src/late/diagnostics.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 6f4388ff05abe..e26bfa6b96d51 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -390,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 { @@ -423,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(); @@ -533,13 +532,13 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { }; let suggestion = - if ["true", "false"].contains(&item_str.to_string().to_lowercase().as_str()) { + 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_str.to_string().to_lowercase(); + 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_str.as_str() == "printf" { + } else if item_ident.as_str() == "printf" { Some(( item_span, "you may have meant to use the `print` macro", @@ -548,8 +547,9 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } else { suggestion }; - let mut msg = - format!("cannot find {expected} `{item_str}` in {mod_prefix}{tick}{mod_str}{tick}"); + 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 { @@ -559,7 +559,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let Some(module_def_id) = module && let Some(directive) = self.r.on_unknown_data(module_def_id) { - let args = FormatArgs { unresolved: item_str.to_string(), this: mod_str, .. }; + let args = FormatArgs { unresolved: item_ident.to_string(), this: mod_str, .. }; let CustomDiagnostic { message, label,