Skip to content
Open
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
77 changes: 64 additions & 13 deletions compiler/rustc_resolve/src/diagnostics/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub(crate) struct ImportSuggestion {
/// An extra note that should be issued if this item is suggested
pub note: Option<String>,
pub is_stable: bool,
pub is_exact_match: bool,

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub is_exact_match: bool,
pub exact_matched: bool,

So that we don't need to consider using is_* or are_* otherwhere.

View changes since the review

}

/// Adjust the impl span so that just the `impl` keyword is taken by removing
Expand Down Expand Up @@ -1590,16 +1591,18 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
}
}

fn lookup_import_candidates_from_module<FilterFn>(
fn lookup_import_candidates_from_module<IdentFilterFn, FilterFn>(
&self,
lookup_ident: Ident,
namespace: Namespace,
parent_scope: &ParentScope<'ra>,
start_module: Module<'ra>,
crate_path: ThinVec<ast::PathSegment>,
ident_filter_fn: IdentFilterFn,

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not necessary, considering we will have only two modes for now. And it's good enough to just use a param like case_sensitvie: CaseSensitive here. CaseSensitive could be an enum.

View changes since the review

filter_fn: FilterFn,
) -> Vec<ImportSuggestion>
where
IdentFilterFn: Fn(Ident, Ident) -> bool,
FilterFn: Fn(Res) -> bool,
{
let mut candidates = Vec::new();
Expand Down Expand Up @@ -1671,7 +1674,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
// collect results based on the filter function
// avoid suggesting anything from the same module in which we are resolving
// avoid suggesting anything with a hygienic name
if ident.name == lookup_ident.name
if ident_filter_fn(ident.orig(orig_ident_span), lookup_ident)
&& ns == namespace
&& in_module != parent_scope.module
&& ident.ctxt.is_root()
Expand Down Expand Up @@ -1751,6 +1754,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
note,
via_import,
is_stable,
is_exact_match: ident.name == lookup_ident.name,
});
}
}
Expand Down Expand Up @@ -1827,6 +1831,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
///
/// N.B., the method does not look into imports, but this is not a problem,
/// since we report the definitions (thus, the de-aliased imports).
///
/// The method is implemented in `lookup_import_candidates_impl`. The `_impl` method allows applying a different filter function on the ident than the exact match function used by default here.
pub(crate) fn lookup_import_candidates<FilterFn>(
&self,
lookup_ident: Ident,
Expand All @@ -1836,6 +1842,28 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
) -> Vec<ImportSuggestion>
where
FilterFn: Fn(Res) -> bool,
{
self.lookup_import_candidates_impl(
lookup_ident,
namespace,
parent_scope,
|ident: Ident, lookup_ident: Ident| ident.name == lookup_ident.name,
filter_fn,
)
}

/// The actual impl of the `lookup_import_candidates function`.
pub(crate) fn lookup_import_candidates_impl<IdentFilterFn, FilterFn>(

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, if this is an impl, we shouldn't use pub(crate) here and call it otherwhere.

I think there are acceptable two ways:

  1. providering two variants, one is lookup_import_candidates, the other may be lookup_import_candidates_case_insensitive.
  2. adding param case_sensitive for lookup_import_candidates.

I prefer the second one, because we could know the result is case-insensitive exactly.

View changes since the review

&self,
lookup_ident: Ident,
namespace: Namespace,
parent_scope: &ParentScope<'ra>,
ident_filter_fn: IdentFilterFn,
filter_fn: FilterFn,
) -> Vec<ImportSuggestion>
where
IdentFilterFn: Fn(Ident, Ident) -> bool,
FilterFn: Fn(Res) -> bool,
{
let crate_path = thin_vec![ast::PathSegment::from_ident(Ident::with_dummy_span(kw::Crate))];
let mut suggestions = self.lookup_import_candidates_from_module(
Expand All @@ -1844,6 +1872,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
parent_scope,
self.graph_root.to_module(),
crate_path,
&ident_filter_fn,
&filter_fn,
);

Expand Down Expand Up @@ -1898,6 +1927,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
parent_scope,
crate_root,
crate_path,
&ident_filter_fn,
&filter_fn,
));
}
Expand Down Expand Up @@ -3899,7 +3929,7 @@ pub(crate) fn import_candidates(
);
}

type PathString<'a> = (String, &'a str, Option<Span>, &'a Option<String>, bool);
type PathString<'a> = (String, &'a str, Option<Span>, &'a Option<String>, bool, bool);

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you convert this tuple to a struct with named fields? Because it has six fields for now.

View changes since the review


/// When an entity with a given name is not available in scope, we search for
/// entities with that name in all crates. This method allows outputting the
Expand Down Expand Up @@ -3935,6 +3965,7 @@ fn show_candidates(
c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))),
&c.note,
c.via_import,
c.is_exact_match,
))
}
} else {
Expand All @@ -3944,6 +3975,7 @@ fn show_candidates(
c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))),
&c.note,
c.via_import,
c.is_exact_match,
))
}
});
Expand Down Expand Up @@ -3975,9 +4007,10 @@ fn show_candidates(

if !accessible_path_strings.is_empty() {
let (determiner, kind, s, name, through) =
if let [(name, descr, _, _, via_import)] = &accessible_path_strings[..] {
if let [(name, descr, _, _, via_import, is_exact_match)] = &accessible_path_strings[..]
{
(
"this",
if *is_exact_match { "this" } else { "this similarly named" },
*descr,
"",
format!(" `{name}`"),
Expand All @@ -3988,12 +4021,24 @@ fn show_candidates(
// instead of the more generic "items".
let kinds = accessible_path_strings
.iter()
.map(|(_, descr, _, _, _)| *descr)
.map(|(_, descr, _, _, _, _)| *descr)
.collect::<UnordSet<&str>>();
let kind = if let Some(kind) = kinds.get_only() { kind } else { "item" };
let s = if kind.ends_with('s') { "es" } else { "s" };
// we should only suggest case insensitive suggestion if no case sensitive match was found,
// so all the suggestion should have the same is_exact_match value.

("one of these", kind, s, String::new(), "")
(
if accessible_path_strings[0].5 {
"one of these"
} else {
"one of these similarly named"
},
kind,
s,
String::new(),
"",
)
};

let instead = if let Instead::Yes = instead { " instead" } else { "" };
Expand Down Expand Up @@ -4087,9 +4132,12 @@ fn show_candidates(
{
let prefix =
if let DiagMode::Pattern = mode { "you might have meant to match on " } else { "" };
if let [(name, descr, source_span, note, _)] = &inaccessible_path_strings[..] {
if let [(name, descr, source_span, note, _, is_exact_match)] =
&inaccessible_path_strings[..]
{
let msg = format!(
"{prefix}{descr} `{name}`{} exists but is inaccessible",
"{prefix}{}{descr} `{name}`{} exists but is inaccessible",
if *is_exact_match { "" } else { "similarly named " },
if let DiagMode::Pattern = mode { ", which" } else { "" }
);

Expand All @@ -4107,17 +4155,20 @@ fn show_candidates(
} else {
let descr = inaccessible_path_strings
.iter()
.map(|&(_, descr, _, _, _)| descr)
.map(|&(_, descr, _, _, _, _)| descr)
.all_equal_value()
.unwrap_or("item");
let plural_descr =
if descr.ends_with('s') { format!("{descr}es") } else { format!("{descr}s") };

let mut msg = format!("{prefix}these {plural_descr} exist but are inaccessible");
let are_exact_matches = inaccessible_path_strings[0].5;
let mut msg = format!(
"{prefix}these {}{plural_descr} exist but are inaccessible",
if are_exact_matches { "" } else { "similarly named " },
);
let mut has_colon = false;

let mut spans = Vec::new();
for (name, _, source_span, _, _) in &inaccessible_path_strings {
for (name, _, source_span, _, _, _) in &inaccessible_path_strings {
if let Some(source_span) = source_span {
let span = tcx.sess.source_map().guess_head_span(*source_span);
spans.push((name, span));
Expand Down
98 changes: 91 additions & 7 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
use rustc_data_structures::unord::UnordItems;
use rustc_errors::codes::*;
use rustc_errors::{
Applicability, Diag, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, pluralize,
struct_span_code_err,
Applicability, Diag, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, Suggestions,
pluralize, struct_span_code_err,
};
use rustc_hir as hir;
use rustc_hir::attrs::diagnostic::{CustomDiagnostic, FormatArgs};
Expand Down Expand Up @@ -760,6 +760,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
};

let (found, suggested_candidates, mut candidates) = self.try_lookup_name_relaxed(
true,
&mut err,
source,
path,
Expand Down Expand Up @@ -788,11 +789,35 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
suggested_candidates,
);

self.err_code_special_cases(&mut err, source, path, span);

let no_suggestion = match &err.suggestions {
Suggestions::Enabled(suggestions) => suggestions.is_empty(),
Suggestions::Sealed(suggestions) => suggestions.is_empty(),
Suggestions::Disabled => false,
};
if let Some(E0425) = err.code
&& candidates.is_empty()
&& no_suggestion
{
Comment on lines +799 to +802

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid this is not enough. Because the code of err may be changed to the parent err's code. And other suggestions may also be added later. And this is why we emit some (unexpected) suggestions in tests/ui/resolve/export-fully-qualified.rs and tests/ui/suggestions/crate-or-module-typo.rs.

So I think a better way is to share most logic between the two branches in try_lookup_name_relaxed, like what I commented. And we could return an additional candidates_case_insensitive in this function.

And finnaly, we could use candidates_case_insensitive only if candidates is empty and the final err.code is Some(E0425) when constructing UseError in fn smart_resolve_path_fragment.

View changes since the review

candidates = self
.try_lookup_name_relaxed(
false,
&mut err,
source,
path,
following_seg,
span,
res,
&base_error,
)
.2;
}

if fallback {
// Fallback label.
err.span_label(base_error.span, base_error.fallback_label);
}
self.err_code_special_cases(&mut err, source, path, span);

let module = base_error.module.unwrap_or_else(|| CRATE_DEF_ID.to_def_id());
self.r.find_cfg_stripped(&mut err, &path.last().unwrap().ident.name, module);
Expand Down Expand Up @@ -916,6 +941,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {

fn try_lookup_name_relaxed(
&mut self,
case_sensitive: bool, // a subset of the tests are run when false

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use an Enum instead? Maybe CaseSensitive::Yes/No.

View changes since the review

err: &mut Diag<'_>,
source: PathSource<'_, '_, '_>,
path: &[Segment],
Expand All @@ -935,16 +961,70 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
let mut suggested_candidates = FxHashSet::default();
// Try to lookup name in more relaxed fashion for better error reporting.
let ident = path.last().unwrap().ident;
let is_expected = &|res| source.is_expected(res);
// we do not suggest alternative capitalizations if only one letter, too many constants can match and it become noisy.
if !case_sensitive && ident.as_str().len() < 2 {
return (false, suggested_candidates, Vec::new());
}

let ident_filter = &|ident: Ident, ident_lookup: Ident| {
if case_sensitive {
ident.name == ident_lookup.name
} else {
ident.name.as_str().to_lowercase() == ident_lookup.name.as_str().to_lowercase()
}
};
let is_expected = &|res| {
if case_sensitive {
source.is_expected(res)
} else {
if following_seg.is_none() {
source.is_expected(res)
} else {
matches!(res, Res::Def(DefKind::Mod, _)) //fixme(GTimothy):check that this is
// necessary/correct
}
}
};
Comment on lines +976 to +987

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep let is_expected = &|res| source.is_expected(res); here.

View changes since the review

let ns = source.namespace();
let is_enum_variant = &|res| matches!(res, Res::Def(DefKind::Variant, _));
let path_str = Segment::names_to_string(path);
let ident_span = path.last().map_or(span, |ident| ident.ident.span);
let mut candidates = self
.r
.lookup_import_candidates(ident, ns, &self.parent_scope, is_expected)
.lookup_import_candidates_impl(ident, ns, &self.parent_scope, ident_filter, is_expected)
.into_iter()
.filter(|ImportSuggestion { did, .. }| {
if !case_sensitive {
// If there's a following segment, only keep modules that contain it
if let Some(following) = following_seg {
let Some(did) = did else { return false };
let Some(module) = self.r.get_module(*did) else { return false };
let mut found = false;
module.for_each_child(self.r, |_, ident, _, _, _| {
if ident.name == following.ident.name {
found = true;
}
});
if !found {
return false;
}
}

// Filter out items that are in the prelude
if let Some(prelude) = self.r.prelude {
if let Some(suggestion_did) = did {
let mut is_in_prelude = false;
prelude.for_each_child(self.r, |_, _, _, _, decl| {
if decl.res().opt_def_id() == Some(*suggestion_did) {
is_in_prelude = true;
}
});
if is_in_prelude {
return false;
}
}
}
}
Comment on lines +997 to +1027

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also unnecessary.

View changes since the review

match (did, res.and_then(|res| res.opt_def_id())) {
(Some(suggestion_did), Some(actual_did)) => *suggestion_did != actual_did,
_ => true,
Expand All @@ -963,6 +1043,9 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
// Put them back if we have no more candidates to suggest...
candidates = intrinsic_candidates;
}
if !case_sensitive {
return (false, suggested_candidates, candidates);
}
Comment on lines +1046 to +1048

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also unneeded.

View changes since the review

let crate_def_id = CRATE_DEF_ID.to_def_id();
if candidates.is_empty() && is_expected(Res::Def(DefKind::Enum, crate_def_id)) {
let mut enum_candidates: Vec<_> = self
Expand Down Expand Up @@ -1153,7 +1236,6 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
}
}
}

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary removal.

View changes since the review

if candidates.is_empty() {
candidates = self.smart_resolve_partial_mod_path_errors(path, following_seg);
}
Expand Down Expand Up @@ -3088,7 +3170,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span)));
let doc_visible = doc_visible
&& (module_def_id.is_local() || !r.tcx.is_doc_hidden(module_def_id));
if module_def_id == def_id {
let is_exact_match = module_def_id == def_id;
if is_exact_match {
Comment on lines +3173 to +3174

@mu001999 mu001999 Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let is_exact_match = module_def_id == def_id;
if is_exact_match {
if module_def_id == def_id {

View changes since the review

let path = Path { span: name_binding.span, segments: path_segments };
result = Some((
r.expect_module(module_def_id),
Expand All @@ -3101,6 +3184,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
note: None,
via_import: false,
is_stable: true,
is_exact_match,
},
));
} else {
Expand Down
7 changes: 7 additions & 0 deletions tests/ui/cast/cast-errors-issue-43825.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ error[E0425]: cannot find value `error` in this scope
|
LL | let error = error;
| ^^^^^ not found in this scope
|
help: consider importing one of these similarly named items
|
LL + use std::fmt::Error;
|
LL + use std::fs::TryLockError::Error;
|
Comment thread
mu001999 marked this conversation as resolved.

error: aborting due to 1 previous error

Expand Down
Loading
Loading