Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_resolve/src/diagnostics/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
95 changes: 66 additions & 29 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -163,6 +164,7 @@ struct BaseError {
could_be_expr: bool,
suggestion: Option<(Span, &'static str, String)>,
module: Option<DefId>,
notes: Vec<String>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -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 {
Expand All @@ -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()
Expand All @@ -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();
Expand Down Expand Up @@ -490,23 +492,30 @@ 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()),
None,
)
}
} 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);
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions tests/ui/diagnostic_namespace/on_unknown/late_res.rs
Original file line number Diff line number Diff line change
@@ -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]
];
}
40 changes: 40 additions & 0 deletions tests/ui/diagnostic_namespace/on_unknown/late_res.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading