-
-
Notifications
You must be signed in to change notification settings - Fork 15.5k
Suggest case insensitive import suggestions #156239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
| } | ||
|
|
||
| /// Adjust the impl span so that just the `impl` keyword is taken by removing | ||
|
|
@@ -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, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| filter_fn: FilterFn, | ||
| ) -> Vec<ImportSuggestion> | ||
| where | ||
| IdentFilterFn: Fn(Ident, Ident) -> bool, | ||
| FilterFn: Fn(Res) -> bool, | ||
| { | ||
| let mut candidates = Vec::new(); | ||
|
|
@@ -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() | ||
|
|
@@ -1751,6 +1754,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { | |
| note, | ||
| via_import, | ||
| is_stable, | ||
| is_exact_match: ident.name == lookup_ident.name, | ||
| }); | ||
| } | ||
| } | ||
|
|
@@ -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, | ||
|
|
@@ -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>( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, if this is an impl, we shouldn't use I think there are acceptable two ways:
I prefer the second one, because we could know the result is case-insensitive exactly. |
||
| &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( | ||
|
|
@@ -1844,6 +1872,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { | |
| parent_scope, | ||
| self.graph_root.to_module(), | ||
| crate_path, | ||
| &ident_filter_fn, | ||
| &filter_fn, | ||
| ); | ||
|
|
||
|
|
@@ -1898,6 +1927,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { | |
| parent_scope, | ||
| crate_root, | ||
| crate_path, | ||
| &ident_filter_fn, | ||
| &filter_fn, | ||
| )); | ||
| } | ||
|
|
@@ -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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
|
||
| /// 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 | ||
|
|
@@ -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 { | ||
|
|
@@ -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, | ||
| )) | ||
| } | ||
| }); | ||
|
|
@@ -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}`"), | ||
|
|
@@ -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 { "" }; | ||
|
|
@@ -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 { "" } | ||
| ); | ||
|
|
||
|
|
@@ -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)); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -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}; | ||||||||
|
|
@@ -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, | ||||||||
|
|
@@ -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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm afraid this is not enough. Because the So I think a better way is to share most logic between the two branches in And finnaly, we could use |
||||||||
| 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); | ||||||||
|
|
@@ -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 | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you use an |
||||||||
| err: &mut Diag<'_>, | ||||||||
| source: PathSource<'_, '_, '_>, | ||||||||
| path: &[Segment], | ||||||||
|
|
@@ -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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep |
||||||||
| 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also unnecessary. |
||||||||
| match (did, res.and_then(|res| res.opt_def_id())) { | ||||||||
| (Some(suggestion_did), Some(actual_did)) => *suggestion_did != actual_did, | ||||||||
| _ => true, | ||||||||
|
|
@@ -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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also unneeded. |
||||||||
| 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 | ||||||||
|
|
@@ -1153,7 +1236,6 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { | |||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary removal. |
||||||||
| if candidates.is_empty() { | ||||||||
| candidates = self.smart_resolve_partial_mod_path_errors(path, following_seg); | ||||||||
| } | ||||||||
|
|
@@ -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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| let path = Path { span: name_binding.span, segments: path_segments }; | ||||||||
| result = Some(( | ||||||||
| r.expect_module(module_def_id), | ||||||||
|
|
@@ -3101,6 +3184,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { | |||||||
| note: None, | ||||||||
| via_import: false, | ||||||||
| is_stable: true, | ||||||||
| is_exact_match, | ||||||||
| }, | ||||||||
| )); | ||||||||
| } else { | ||||||||
|
|
||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So that we don't need to consider using
is_*orare_*otherwhere.View changes since the review