diff --git a/src/librustc/hir/def.rs b/src/librustc/hir/def.rs index 131a910bebb1a..db568dbf9fb94 100644 --- a/src/librustc/hir/def.rs +++ b/src/librustc/hir/def.rs @@ -398,4 +398,12 @@ impl Res { Res::Err => Res::Err, } } + + pub fn macro_kind(self) -> Option { + match self { + Res::Def(DefKind::Macro(kind), _) => Some(kind), + Res::NonMacroAttr(..) => Some(MacroKind::Attr), + _ => None, + } + } } diff --git a/src/librustc_resolve/build_reduced_graph.rs b/src/librustc_resolve/build_reduced_graph.rs index 16fd8cccc8920..4554980071272 100644 --- a/src/librustc_resolve/build_reduced_graph.rs +++ b/src/librustc_resolve/build_reduced_graph.rs @@ -3,11 +3,11 @@ //! Here we build the "reduced graph": the graph of the module tree without //! any imports resolved. -use crate::macros::{InvocationData, ParentScope, LegacyScope}; +use crate::macros::{InvocationData, LegacyScope}; use crate::resolve_imports::ImportDirective; use crate::resolve_imports::ImportDirectiveSubclass::{self, GlobImport, SingleImport}; use crate::{Module, ModuleData, ModuleKind, NameBinding, NameBindingKind, Segment, ToNameBinding}; -use crate::{ModuleOrUniformRoot, PerNS, Resolver, ResolverArenas, ExternPreludeEntry}; +use crate::{ModuleOrUniformRoot, ParentScope, PerNS, Resolver, ResolverArenas, ExternPreludeEntry}; use crate::Namespace::{self, TypeNS, ValueNS, MacroNS}; use crate::{resolve_error, resolve_struct_error, ResolutionError, Determinacy}; diff --git a/src/librustc_resolve/diagnostics.rs b/src/librustc_resolve/diagnostics.rs index e93a2315ca321..90a4107f773e1 100644 --- a/src/librustc_resolve/diagnostics.rs +++ b/src/librustc_resolve/diagnostics.rs @@ -2,21 +2,84 @@ use std::cmp::Reverse; use errors::{Applicability, DiagnosticBuilder, DiagnosticId}; use log::debug; -use rustc::hir::def::{self, DefKind, CtorKind, Namespace::*}; +use rustc::hir::def::{self, DefKind, CtorKind, NonMacroAttrKind}; +use rustc::hir::def::Namespace::{self, *}; use rustc::hir::def_id::{CRATE_DEF_INDEX, DefId}; +use rustc::hir::PrimTy; use rustc::session::{Session, config::nightly_options}; -use syntax::ast::{self, Expr, ExprKind, Ident}; +use rustc::ty::{self, DefIdTree}; +use rustc::util::nodemap::FxHashSet; +use syntax::ast::{self, Expr, ExprKind, Ident, NodeId, Path, Ty, TyKind}; use syntax::ext::base::MacroKind; +use syntax::feature_gate::BUILTIN_ATTRIBUTES; use syntax::symbol::{Symbol, kw}; +use syntax::util::lev_distance::find_best_match_for_name; use syntax_pos::{BytePos, Span}; +use crate::resolve_imports::{ImportDirective, ImportDirectiveSubclass, ImportResolver}; +use crate::{is_self_type, is_self_value, path_names_to_string, KNOWN_TOOLS}; +use crate::{CrateLint, LegacyScope, Module, ModuleKind, ModuleOrUniformRoot}; +use crate::{PathResult, PathSource, ParentScope, Resolver, RibKind, Scope, ScopeSet, Segment}; + type Res = def::Res; -use crate::macros::ParentScope; -use crate::resolve_imports::{ImportDirective, ImportDirectiveSubclass, ImportResolver}; -use crate::{import_candidate_to_enum_paths, is_self_type, is_self_value, path_names_to_string}; -use crate::{AssocSuggestion, CrateLint, ImportSuggestion, ModuleOrUniformRoot, PathResult, - PathSource, Resolver, Segment, Suggestion}; +/// A vector of spans and replacements, a message and applicability. +crate type Suggestion = (Vec<(Span, String)>, String, Applicability); + +/// A field or associated item from self type suggested in case of resolution failure. +enum AssocSuggestion { + Field, + MethodWithSelf, + AssocItem, +} + +struct TypoSuggestion { + candidate: Symbol, + + /// The kind of the binding ("crate", "module", etc.) + kind: &'static str, + + /// An appropriate article to refer to the binding ("a", "an", etc.) + article: &'static str, +} + +impl TypoSuggestion { + fn from_res(candidate: Symbol, res: Res) -> TypoSuggestion { + TypoSuggestion { candidate, kind: res.descr(), article: res.article() } + } +} + +/// A free importable items suggested in case of resolution failure. +crate struct ImportSuggestion { + did: Option, + pub path: Path, +} + +fn add_typo_suggestion( + err: &mut DiagnosticBuilder<'_>, suggestion: Option, span: Span +) -> bool { + if let Some(suggestion) = suggestion { + let msg = format!("{} {} with a similar name exists", suggestion.article, suggestion.kind); + err.span_suggestion( + span, &msg, suggestion.candidate.to_string(), Applicability::MaybeIncorrect + ); + return true; + } + false +} + +fn add_module_candidates( + module: Module<'_>, names: &mut Vec, filter_fn: &impl Fn(Res) -> bool +) { + for (&(ident, _), resolution) in module.resolutions.borrow().iter() { + if let Some(binding) = resolution.borrow().binding { + let res = binding.res(); + if filter_fn(res) { + names.push(TypoSuggestion::from_res(ident.name, res)); + } + } + } +} impl<'a> Resolver<'a> { /// Handles error reporting for `smart_resolve_path_fragment` function. @@ -204,24 +267,10 @@ impl<'a> Resolver<'a> { } } - let mut levenshtein_worked = false; - // Try Levenshtein algorithm. - let suggestion = self.lookup_typo_candidate(path, ns, is_expected, span); - if let Some(suggestion) = suggestion { - let msg = format!( - "{} {} with a similar name exists", - suggestion.article, suggestion.kind - ); - err.span_suggestion( - ident_span, - &msg, - suggestion.candidate.to_string(), - Applicability::MaybeIncorrect, - ); - - levenshtein_worked = true; - } + let levenshtein_worked = add_typo_suggestion( + &mut err, self.lookup_typo_candidate(path, ns, is_expected, span), ident_span + ); // Try context-dependent help if relaxed lookup didn't work. if let Some(res) = res { @@ -365,7 +414,7 @@ impl<'a> Resolver<'a> { }; match (res, source) { - (Res::Def(DefKind::Macro(..), _), _) => { + (Res::Def(DefKind::Macro(MacroKind::Bang), _), _) => { err.span_suggestion( span, "use `!` to invoke the macro", @@ -439,6 +488,513 @@ impl<'a> Resolver<'a> { } true } + + fn lookup_assoc_candidate(&mut self, + ident: Ident, + ns: Namespace, + filter_fn: FilterFn) + -> Option + where FilterFn: Fn(Res) -> bool + { + fn extract_node_id(t: &Ty) -> Option { + match t.node { + TyKind::Path(None, _) => Some(t.id), + TyKind::Rptr(_, ref mut_ty) => extract_node_id(&mut_ty.ty), + // This doesn't handle the remaining `Ty` variants as they are not + // that commonly the self_type, it might be interesting to provide + // support for those in future. + _ => None, + } + } + + // Fields are generally expected in the same contexts as locals. + if filter_fn(Res::Local(ast::DUMMY_NODE_ID)) { + if let Some(node_id) = self.current_self_type.as_ref().and_then(extract_node_id) { + // Look for a field with the same name in the current self_type. + if let Some(resolution) = self.partial_res_map.get(&node_id) { + match resolution.base_res() { + Res::Def(DefKind::Struct, did) | Res::Def(DefKind::Union, did) + if resolution.unresolved_segments() == 0 => { + if let Some(field_names) = self.field_names.get(&did) { + if field_names.iter().any(|&field_name| ident.name == field_name) { + return Some(AssocSuggestion::Field); + } + } + } + _ => {} + } + } + } + } + + for assoc_type_ident in &self.current_trait_assoc_types { + if *assoc_type_ident == ident { + return Some(AssocSuggestion::AssocItem); + } + } + + // Look for associated items in the current trait. + if let Some((module, _)) = self.current_trait_ref { + if let Ok(binding) = self.resolve_ident_in_module( + ModuleOrUniformRoot::Module(module), + ident, + ns, + None, + false, + module.span, + ) { + let res = binding.res(); + if filter_fn(res) { + return Some(if self.has_self.contains(&res.def_id()) { + AssocSuggestion::MethodWithSelf + } else { + AssocSuggestion::AssocItem + }); + } + } + } + + None + } + + /// Lookup typo candidate in scope for a macro or import. + fn early_lookup_typo_candidate( + &mut self, + scope_set: ScopeSet, + parent_scope: &ParentScope<'a>, + ident: Ident, + filter_fn: &impl Fn(Res) -> bool, + ) -> Option { + let mut suggestions = Vec::new(); + self.visit_scopes(scope_set, parent_scope, ident, |this, scope, _| { + match scope { + Scope::DeriveHelpers => { + let res = Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper); + if filter_fn(res) { + for derive in &parent_scope.derives { + let parent_scope = ParentScope { derives: Vec::new(), ..*parent_scope }; + if let Ok((Some(ext), _)) = this.resolve_macro_path( + derive, Some(MacroKind::Derive), &parent_scope, false, false + ) { + suggestions.extend(ext.helper_attrs.iter().map(|name| { + TypoSuggestion::from_res(*name, res) + })); + } + } + } + } + Scope::MacroRules(legacy_scope) => { + if let LegacyScope::Binding(legacy_binding) = legacy_scope { + let res = legacy_binding.binding.res(); + if filter_fn(res) { + suggestions.push( + TypoSuggestion::from_res(legacy_binding.ident.name, res) + ) + } + } + } + Scope::CrateRoot => { + let root_ident = Ident::new(kw::PathRoot, ident.span); + let root_module = this.resolve_crate_root(root_ident); + add_module_candidates(root_module, &mut suggestions, filter_fn); + } + Scope::Module(module) => { + add_module_candidates(module, &mut suggestions, filter_fn); + } + Scope::MacroUsePrelude => { + suggestions.extend(this.macro_use_prelude.iter().filter_map(|(name, binding)| { + let res = binding.res(); + if filter_fn(res) { + Some(TypoSuggestion::from_res(*name, res)) + } else { + None + } + })); + } + Scope::BuiltinMacros => { + suggestions.extend(this.builtin_macros.iter().filter_map(|(name, binding)| { + let res = binding.res(); + if filter_fn(res) { + Some(TypoSuggestion::from_res(*name, res)) + } else { + None + } + })); + } + Scope::BuiltinAttrs => { + let res = Res::NonMacroAttr(NonMacroAttrKind::Builtin); + if filter_fn(res) { + suggestions.extend(BUILTIN_ATTRIBUTES.iter().map(|(name, ..)| { + TypoSuggestion::from_res(*name, res) + })); + } + } + Scope::LegacyPluginHelpers => { + let res = Res::NonMacroAttr(NonMacroAttrKind::LegacyPluginHelper); + if filter_fn(res) { + let plugin_attributes = this.session.plugin_attributes.borrow(); + suggestions.extend(plugin_attributes.iter().map(|(name, _)| { + TypoSuggestion::from_res(*name, res) + })); + } + } + Scope::ExternPrelude => { + suggestions.extend(this.extern_prelude.iter().filter_map(|(ident, _)| { + let res = Res::Def(DefKind::Mod, DefId::local(CRATE_DEF_INDEX)); + if filter_fn(res) { + Some(TypoSuggestion::from_res(ident.name, res)) + } else { + None + } + })); + } + Scope::ToolPrelude => { + let res = Res::NonMacroAttr(NonMacroAttrKind::Tool); + suggestions.extend(KNOWN_TOOLS.iter().map(|name| { + TypoSuggestion::from_res(*name, res) + })); + } + Scope::StdLibPrelude => { + if let Some(prelude) = this.prelude { + add_module_candidates(prelude, &mut suggestions, filter_fn); + } + } + Scope::BuiltinTypes => { + let primitive_types = &this.primitive_type_table.primitive_types; + suggestions.extend( + primitive_types.iter().flat_map(|(name, prim_ty)| { + let res = Res::PrimTy(*prim_ty); + if filter_fn(res) { + Some(TypoSuggestion::from_res(*name, res)) + } else { + None + } + }) + ) + } + } + + None::<()> + }); + + // Make sure error reporting is deterministic. + suggestions.sort_by_cached_key(|suggestion| suggestion.candidate.as_str()); + + match find_best_match_for_name( + suggestions.iter().map(|suggestion| &suggestion.candidate), + &ident.as_str(), + None, + ) { + Some(found) if found != ident.name => suggestions + .into_iter() + .find(|suggestion| suggestion.candidate == found), + _ => None, + } + } + + fn lookup_typo_candidate( + &mut self, + path: &[Segment], + ns: Namespace, + filter_fn: &impl Fn(Res) -> bool, + span: Span, + ) -> Option { + let mut names = Vec::new(); + if path.len() == 1 { + // Search in lexical scope. + // Walk backwards up the ribs in scope and collect candidates. + for rib in self.ribs[ns].iter().rev() { + // Locals and type parameters + for (ident, &res) in &rib.bindings { + if filter_fn(res) { + names.push(TypoSuggestion::from_res(ident.name, res)); + } + } + // Items in scope + if let RibKind::ModuleRibKind(module) = rib.kind { + // Items from this module + add_module_candidates(module, &mut names, &filter_fn); + + if let ModuleKind::Block(..) = module.kind { + // We can see through blocks + } else { + // Items from the prelude + if !module.no_implicit_prelude { + names.extend(self.extern_prelude.clone().iter().flat_map(|(ident, _)| { + self.crate_loader + .maybe_process_path_extern(ident.name, ident.span) + .and_then(|crate_id| { + let crate_mod = Res::Def( + DefKind::Mod, + DefId { + krate: crate_id, + index: CRATE_DEF_INDEX, + }, + ); + + if filter_fn(crate_mod) { + Some(TypoSuggestion { + candidate: ident.name, + article: "a", + kind: "crate", + }) + } else { + None + } + }) + })); + + if let Some(prelude) = self.prelude { + add_module_candidates(prelude, &mut names, &filter_fn); + } + } + break; + } + } + } + // Add primitive types to the mix + if filter_fn(Res::PrimTy(PrimTy::Bool)) { + names.extend( + self.primitive_type_table.primitive_types.iter().map(|(name, prim_ty)| { + TypoSuggestion::from_res(*name, Res::PrimTy(*prim_ty)) + }) + ) + } + } else { + // Search in module. + let mod_path = &path[..path.len() - 1]; + if let PathResult::Module(module) = self.resolve_path_without_parent_scope( + mod_path, Some(TypeNS), false, span, CrateLint::No + ) { + if let ModuleOrUniformRoot::Module(module) = module { + add_module_candidates(module, &mut names, &filter_fn); + } + } + } + + let name = path[path.len() - 1].ident.name; + // Make sure error reporting is deterministic. + names.sort_by_cached_key(|suggestion| suggestion.candidate.as_str()); + + match find_best_match_for_name( + names.iter().map(|suggestion| &suggestion.candidate), + &name.as_str(), + None, + ) { + Some(found) if found != name => names + .into_iter() + .find(|suggestion| suggestion.candidate == found), + _ => None, + } + } + + fn lookup_import_candidates_from_module(&mut self, + lookup_ident: Ident, + namespace: Namespace, + start_module: Module<'a>, + crate_name: Ident, + filter_fn: FilterFn) + -> Vec + where FilterFn: Fn(Res) -> bool + { + let mut candidates = Vec::new(); + let mut seen_modules = FxHashSet::default(); + let not_local_module = crate_name.name != kw::Crate; + let mut worklist = vec![(start_module, Vec::::new(), not_local_module)]; + + while let Some((in_module, + path_segments, + in_module_is_extern)) = worklist.pop() { + self.populate_module_if_necessary(in_module); + + // We have to visit module children in deterministic order to avoid + // instabilities in reported imports (#43552). + in_module.for_each_child_stable(|ident, ns, name_binding| { + // avoid imports entirely + if name_binding.is_import() && !name_binding.is_extern_crate() { return; } + // avoid non-importable candidates as well + if !name_binding.is_importable() { return; } + + // collect results based on the filter function + if ident.name == lookup_ident.name && ns == namespace { + let res = name_binding.res(); + if filter_fn(res) { + // create the path + let mut segms = path_segments.clone(); + if lookup_ident.span.rust_2018() { + // crate-local absolute paths start with `crate::` in edition 2018 + // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) + segms.insert( + 0, ast::PathSegment::from_ident(crate_name) + ); + } + + segms.push(ast::PathSegment::from_ident(ident)); + let path = Path { + span: name_binding.span, + segments: segms, + }; + // the entity is accessible in the following cases: + // 1. if it's defined in the same crate, it's always + // accessible (since private entities can be made public) + // 2. if it's defined in another crate, it's accessible + // only if both the module is public and the entity is + // declared as public (due to pruning, we don't explore + // outside crate private modules => no need to check this) + if !in_module_is_extern || name_binding.vis == ty::Visibility::Public { + let did = match res { + Res::Def(DefKind::Ctor(..), did) => self.parent(did), + _ => res.opt_def_id(), + }; + candidates.push(ImportSuggestion { did, path }); + } + } + } + + // collect submodules to explore + if let Some(module) = name_binding.module() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments.push(ast::PathSegment::from_ident(ident)); + + let is_extern_crate_that_also_appears_in_prelude = + name_binding.is_extern_crate() && + lookup_ident.span.rust_2018(); + + let is_visible_to_user = + !in_module_is_extern || name_binding.vis == ty::Visibility::Public; + + if !is_extern_crate_that_also_appears_in_prelude && is_visible_to_user { + // add the module to the lookup + let is_extern = in_module_is_extern || name_binding.is_extern_crate(); + if seen_modules.insert(module.def_id().unwrap()) { + worklist.push((module, path_segments, is_extern)); + } + } + } + }) + } + + candidates + } + + /// When name resolution fails, this method can be used to look up candidate + /// entities with the expected name. It allows filtering them using the + /// supplied predicate (which should be used to only accept the types of + /// definitions expected, e.g., traits). The lookup spans across all crates. + /// + /// 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). + crate fn lookup_import_candidates( + &mut self, lookup_ident: Ident, namespace: Namespace, filter_fn: FilterFn + ) -> Vec + where FilterFn: Fn(Res) -> bool + { + let mut suggestions = self.lookup_import_candidates_from_module( + lookup_ident, namespace, self.graph_root, Ident::with_empty_ctxt(kw::Crate), &filter_fn + ); + + if lookup_ident.span.rust_2018() { + let extern_prelude_names = self.extern_prelude.clone(); + for (ident, _) in extern_prelude_names.into_iter() { + if let Some(crate_id) = self.crate_loader.maybe_process_path_extern(ident.name, + ident.span) { + let crate_root = self.get_module(DefId { + krate: crate_id, + index: CRATE_DEF_INDEX, + }); + self.populate_module_if_necessary(&crate_root); + + suggestions.extend(self.lookup_import_candidates_from_module( + lookup_ident, namespace, crate_root, ident, &filter_fn)); + } + } + } + + suggestions + } + + fn find_module(&mut self, def_id: DefId) -> Option<(Module<'a>, ImportSuggestion)> { + let mut result = None; + let mut seen_modules = FxHashSet::default(); + let mut worklist = vec![(self.graph_root, Vec::new())]; + + while let Some((in_module, path_segments)) = worklist.pop() { + // abort if the module is already found + if result.is_some() { break; } + + self.populate_module_if_necessary(in_module); + + in_module.for_each_child_stable(|ident, _, name_binding| { + // abort if the module is already found or if name_binding is private external + if result.is_some() || !name_binding.vis.is_visible_locally() { + return + } + if let Some(module) = name_binding.module() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments.push(ast::PathSegment::from_ident(ident)); + let module_def_id = module.def_id().unwrap(); + if module_def_id == def_id { + let path = Path { + span: name_binding.span, + segments: path_segments, + }; + result = Some((module, ImportSuggestion { did: Some(def_id), path })); + } else { + // add the module to the lookup + if seen_modules.insert(module_def_id) { + worklist.push((module, path_segments)); + } + } + } + }); + } + + result + } + + fn collect_enum_variants(&mut self, def_id: DefId) -> Option> { + self.find_module(def_id).map(|(enum_module, enum_import_suggestion)| { + self.populate_module_if_necessary(enum_module); + + let mut variants = Vec::new(); + enum_module.for_each_child_stable(|ident, _, name_binding| { + if let Res::Def(DefKind::Variant, _) = name_binding.res() { + let mut segms = enum_import_suggestion.path.segments.clone(); + segms.push(ast::PathSegment::from_ident(ident)); + variants.push(Path { + span: name_binding.span, + segments: segms, + }); + } + }); + variants + }) + } + + crate fn unresolved_macro_suggestions( + &mut self, + err: &mut DiagnosticBuilder<'a>, + macro_kind: MacroKind, + parent_scope: &ParentScope<'a>, + ident: Ident, + ) { + let is_expected = &|res: Res| res.macro_kind() == Some(macro_kind); + let suggestion = self.early_lookup_typo_candidate( + ScopeSet::Macro(macro_kind), &parent_scope, ident, is_expected + ); + add_typo_suggestion(err, suggestion, ident.span); + + if macro_kind == MacroKind::Derive && + (ident.as_str() == "Send" || ident.as_str() == "Sync") { + let msg = format!("unsafe traits like `{}` should be implemented explicitly", ident); + err.span_note(ident.span, &msg); + } + if self.macro_names.contains(&ident.modern()) { + err.help("have you added the `#[macro_use]` on the module/import?"); + } + } } impl<'a, 'b> ImportResolver<'a, 'b> { @@ -871,3 +1427,70 @@ fn find_span_immediately_after_crate_name( (next_left_bracket == after_second_colon, from_second_colon) } + +/// Gets the stringified path for an enum from an `ImportSuggestion` for an enum variant. +fn import_candidate_to_enum_paths(suggestion: &ImportSuggestion) -> (String, String) { + let variant_path = &suggestion.path; + let variant_path_string = path_names_to_string(variant_path); + + let path_len = suggestion.path.segments.len(); + let enum_path = ast::Path { + span: suggestion.path.span, + segments: suggestion.path.segments[0..path_len - 1].to_vec(), + }; + let enum_path_string = path_names_to_string(&enum_path); + + (variant_path_string, enum_path_string) +} + +/// 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 +/// results of this search in a programmer-friendly way +crate fn show_candidates( + err: &mut DiagnosticBuilder<'_>, + // This is `None` if all placement locations are inside expansions + span: Option, + candidates: &[ImportSuggestion], + better: bool, + found_use: bool, +) { + // we want consistent results across executions, but candidates are produced + // by iterating through a hash map, so make sure they are ordered: + let mut path_strings: Vec<_> = + candidates.into_iter().map(|c| path_names_to_string(&c.path)).collect(); + path_strings.sort(); + + let better = if better { "better " } else { "" }; + let msg_diff = match path_strings.len() { + 1 => " is found in another module, you can import it", + _ => "s are found in other modules, you can import them", + }; + let msg = format!("possible {}candidate{} into scope", better, msg_diff); + + if let Some(span) = span { + for candidate in &mut path_strings { + // produce an additional newline to separate the new use statement + // from the directly following item. + let additional_newline = if found_use { + "" + } else { + "\n" + }; + *candidate = format!("use {};\n{}", candidate, additional_newline); + } + + err.span_suggestions( + span, + &msg, + path_strings.into_iter(), + Applicability::Unspecified, + ); + } else { + let mut msg = msg; + msg.push(':'); + for candidate in path_strings { + msg.push('\n'); + msg.push_str(&candidate); + } + } +} diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index f867fb2078549..ba8cfdcf53548 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -31,7 +31,7 @@ use rustc::hir::def::{ use rustc::hir::def::Namespace::*; use rustc::hir::def_id::{CRATE_DEF_INDEX, LOCAL_CRATE, DefId}; use rustc::hir::{TraitCandidate, TraitMap, GlobMap}; -use rustc::ty::{self, DefIdTree}; +use rustc::ty; use rustc::util::nodemap::{NodeMap, NodeSet, FxHashMap, FxHashSet, DefIdMap}; use rustc::{bug, span_bug}; @@ -69,9 +69,10 @@ use rustc_data_structures::ptr_key::PtrKey; use rustc_data_structures::sync::Lrc; use smallvec::SmallVec; +use diagnostics::{Suggestion, ImportSuggestion}; use diagnostics::{find_span_of_binding_until_next_binding, extend_span_to_previous_binding}; use resolve_imports::{ImportDirective, ImportDirectiveSubclass, NameResolution, ImportResolver}; -use macros::{InvocationData, LegacyBinding, ParentScope}; +use macros::{InvocationData, LegacyBinding, LegacyScope}; type Res = def::Res; @@ -84,9 +85,7 @@ mod check_unused; mod build_reduced_graph; mod resolve_imports; -fn is_known_tool(name: Name) -> bool { - ["clippy", "rustfmt"].contains(&&*name.as_str()) -} +const KNOWN_TOOLS: &[Name] = &[sym::clippy, sym::rustfmt]; enum Weak { Yes, @@ -105,6 +104,29 @@ impl Determinacy { } } +/// A specific scope in which a name can be looked up. +/// This enum is currently used only for early resolution (imports and macros), +/// but not for late resolution yet. +#[derive(Clone, Copy)] +enum Scope<'a> { + DeriveHelpers, + MacroRules(LegacyScope<'a>), + CrateRoot, + Module(Module<'a>), + MacroUsePrelude, + BuiltinMacros, + BuiltinAttrs, + LegacyPluginHelpers, + ExternPrelude, + ToolPrelude, + StdLibPrelude, + BuiltinTypes, +} + +/// Names from different contexts may want to visit different subsets of all specific scopes +/// with different restrictions when looking up the resolution. +/// This enum is currently used only for early resolution (imports and macros), +/// but not for late resolution yet. enum ScopeSet { Import(Namespace), AbsolutePath(Namespace), @@ -112,17 +134,16 @@ enum ScopeSet { Module, } -/// A free importable items suggested in case of resolution failure. -struct ImportSuggestion { - did: Option, - path: Path, -} - -/// A field or associated item from self type suggested in case of resolution failure. -enum AssocSuggestion { - Field, - MethodWithSelf, - AssocItem, +/// Everything you need to know about a name's location to resolve it. +/// Serves as a starting point for the scope visitor. +/// This struct is currently used only for early resolution (imports and macros), +/// but not for late resolution yet. +#[derive(Clone, Debug)] +pub struct ParentScope<'a> { + module: Module<'a>, + expansion: Mark, + legacy: LegacyScope<'a>, + derives: Vec, } #[derive(Eq)] @@ -132,16 +153,6 @@ struct BindingError { target: BTreeSet, } -struct TypoSuggestion { - candidate: Symbol, - - /// The kind of the binding ("crate", "module", etc.) - kind: &'static str, - - /// An appropriate article to refer to the binding ("a", "an", etc.) - article: &'static str, -} - impl PartialOrd for BindingError { fn partial_cmp(&self, other: &BindingError) -> Option { Some(self.cmp(other)) @@ -160,9 +171,6 @@ impl Ord for BindingError { } } -/// A vector of spans and replacements, a message and applicability. -type Suggestion = (Vec<(Span, String)>, String, Applicability); - enum ResolutionError<'a> { /// Error E0401: can't use type or const parameters from outer function. GenericParamsFromOuterFunction(Res), @@ -1488,11 +1496,7 @@ impl<'a> NameBinding<'a> { } fn macro_kind(&self) -> Option { - match self.res() { - Res::Def(DefKind::Macro(kind), _) => Some(kind), - Res::NonMacroAttr(..) => Some(MacroKind::Attr), - _ => None, - } + self.res().macro_kind() } fn descr(&self) -> &'static str { @@ -2134,6 +2138,149 @@ impl<'a> Resolver<'a> { } } + /// A generic scope visitor. + /// Visits scopes in order to resolve some identifier in them or perform other actions. + /// If the callback returns `Some` result, we stop visiting scopes and return it. + fn visit_scopes( + &mut self, + scope_set: ScopeSet, + parent_scope: &ParentScope<'a>, + ident: Ident, + mut visitor: impl FnMut(&mut Self, Scope<'a>, Ident) -> Option, + ) -> Option { + // General principles: + // 1. Not controlled (user-defined) names should have higher priority than controlled names + // built into the language or standard library. This way we can add new names into the + // language or standard library without breaking user code. + // 2. "Closed set" below means new names cannot appear after the current resolution attempt. + // Places to search (in order of decreasing priority): + // (Type NS) + // 1. FIXME: Ribs (type parameters), there's no necessary infrastructure yet + // (open set, not controlled). + // 2. Names in modules (both normal `mod`ules and blocks), loop through hygienic parents + // (open, not controlled). + // 3. Extern prelude (open, the open part is from macro expansions, not controlled). + // 4. Tool modules (closed, controlled right now, but not in the future). + // 5. Standard library prelude (de-facto closed, controlled). + // 6. Language prelude (closed, controlled). + // (Value NS) + // 1. FIXME: Ribs (local variables), there's no necessary infrastructure yet + // (open set, not controlled). + // 2. Names in modules (both normal `mod`ules and blocks), loop through hygienic parents + // (open, not controlled). + // 3. Standard library prelude (de-facto closed, controlled). + // (Macro NS) + // 1-3. Derive helpers (open, not controlled). All ambiguities with other names + // are currently reported as errors. They should be higher in priority than preludes + // and probably even names in modules according to the "general principles" above. They + // also should be subject to restricted shadowing because are effectively produced by + // derives (you need to resolve the derive first to add helpers into scope), but they + // should be available before the derive is expanded for compatibility. + // It's mess in general, so we are being conservative for now. + // 1-3. `macro_rules` (open, not controlled), loop through legacy scopes. Have higher + // priority than prelude macros, but create ambiguities with macros in modules. + // 1-3. Names in modules (both normal `mod`ules and blocks), loop through hygienic parents + // (open, not controlled). Have higher priority than prelude macros, but create + // ambiguities with `macro_rules`. + // 4. `macro_use` prelude (open, the open part is from macro expansions, not controlled). + // 4a. User-defined prelude from macro-use + // (open, the open part is from macro expansions, not controlled). + // 4b. Standard library prelude is currently implemented as `macro-use` (closed, controlled) + // 5. Language prelude: builtin macros (closed, controlled, except for legacy plugins). + // 6. Language prelude: builtin attributes (closed, controlled). + // 4-6. Legacy plugin helpers (open, not controlled). Similar to derive helpers, + // but introduced by legacy plugins using `register_attribute`. Priority is somewhere + // in prelude, not sure where exactly (creates ambiguities with any other prelude names). + + let rust_2015 = ident.span.rust_2015(); + let (ns, is_absolute_path) = match scope_set { + ScopeSet::Import(ns) => (ns, false), + ScopeSet::AbsolutePath(ns) => (ns, true), + ScopeSet::Macro(_) => (MacroNS, false), + ScopeSet::Module => (TypeNS, false), + }; + let mut scope = match ns { + _ if is_absolute_path => Scope::CrateRoot, + TypeNS | ValueNS => Scope::Module(parent_scope.module), + MacroNS => Scope::DeriveHelpers, + }; + let mut ident = ident.modern(); + let mut use_prelude = !parent_scope.module.no_implicit_prelude; + + loop { + let visit = match scope { + Scope::DeriveHelpers => true, + Scope::MacroRules(..) => true, + Scope::CrateRoot => true, + Scope::Module(..) => true, + Scope::MacroUsePrelude => use_prelude || rust_2015, + Scope::BuiltinMacros => true, + Scope::BuiltinAttrs => true, + Scope::LegacyPluginHelpers => use_prelude || rust_2015, + Scope::ExternPrelude => use_prelude || is_absolute_path, + Scope::ToolPrelude => use_prelude, + Scope::StdLibPrelude => use_prelude, + Scope::BuiltinTypes => true, + }; + + if visit { + if let break_result @ Some(..) = visitor(self, scope, ident) { + return break_result; + } + } + + scope = match scope { + Scope::DeriveHelpers => + Scope::MacroRules(parent_scope.legacy), + Scope::MacroRules(legacy_scope) => match legacy_scope { + LegacyScope::Binding(binding) => Scope::MacroRules( + binding.parent_legacy_scope + ), + LegacyScope::Invocation(invoc) => Scope::MacroRules( + invoc.output_legacy_scope.get().unwrap_or(invoc.parent_legacy_scope) + ), + LegacyScope::Empty => Scope::Module(parent_scope.module), + } + Scope::CrateRoot => match ns { + TypeNS => { + ident.span.adjust(Mark::root()); + Scope::ExternPrelude + } + ValueNS | MacroNS => break, + } + Scope::Module(module) => { + use_prelude = !module.no_implicit_prelude; + match self.hygienic_lexical_parent(module, &mut ident.span) { + Some(parent_module) => Scope::Module(parent_module), + None => { + ident.span.adjust(Mark::root()); + match ns { + TypeNS => Scope::ExternPrelude, + ValueNS => Scope::StdLibPrelude, + MacroNS => Scope::MacroUsePrelude, + } + } + } + } + Scope::MacroUsePrelude => Scope::StdLibPrelude, + Scope::BuiltinMacros => Scope::BuiltinAttrs, + Scope::BuiltinAttrs => Scope::LegacyPluginHelpers, + Scope::LegacyPluginHelpers => break, // nowhere else to search + Scope::ExternPrelude if is_absolute_path => break, + Scope::ExternPrelude => Scope::ToolPrelude, + Scope::ToolPrelude => Scope::StdLibPrelude, + Scope::StdLibPrelude => match ns { + TypeNS => Scope::BuiltinTypes, + ValueNS => break, // nowhere else to search + MacroNS => Scope::BuiltinMacros, + } + Scope::BuiltinTypes => break, // nowhere else to search + }; + } + + None + } + /// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope. /// More specifically, we proceed up the hierarchy of scopes and return the binding for /// `ident` in the first scope that defines it (or None if no scopes define it). @@ -2258,7 +2405,7 @@ impl<'a> Resolver<'a> { return Some(LexicalScopeBinding::Item(binding)); } } - if ns == TypeNS && is_known_tool(ident.name) { + if ns == TypeNS && KNOWN_TOOLS.contains(&ident.name) { let binding = (Res::ToolMod, ty::Visibility::Public, DUMMY_SP, Mark::root()).to_name_binding(self.arenas); return Some(LexicalScopeBinding::Item(binding)); @@ -3517,9 +3664,7 @@ impl<'a> Resolver<'a> { crate_lint: CrateLint, ) -> Option { let mut fin_res = None; - // FIXME: can't resolve paths in macro namespace yet, macros are - // processed by the little special hack below. - for (i, ns) in [primary_ns, TypeNS, ValueNS, /*MacroNS*/].iter().cloned().enumerate() { + for (i, ns) in [primary_ns, TypeNS, ValueNS].iter().cloned().enumerate() { if i == 0 || ns != primary_ns { match self.resolve_qpath(id, qself, path, ns, span, global_by_default, crate_lint) { // If defer_to_typeck, then resolution > no resolution, @@ -3528,21 +3673,23 @@ impl<'a> Resolver<'a> { defer_to_typeck => return Some(partial_res), partial_res => if fin_res.is_none() { fin_res = partial_res }, - }; + } } } - if primary_ns != MacroNS && - (self.macro_names.contains(&path[0].ident.modern()) || - self.builtin_macros.get(&path[0].ident.name).cloned() - .and_then(NameBinding::macro_kind) == Some(MacroKind::Bang) || - self.macro_use_prelude.get(&path[0].ident.name).cloned() - .and_then(NameBinding::macro_kind) == Some(MacroKind::Bang)) { - // Return some dummy definition, it's enough for error reporting. - return Some(PartialRes::new(Res::Def( - DefKind::Macro(MacroKind::Bang), - DefId::local(CRATE_DEF_INDEX), - ))); + + // `MacroNS` + assert!(primary_ns != MacroNS); + if qself.is_none() { + let path_seg = |seg: &Segment| ast::PathSegment::from_ident(seg.ident); + let path = Path { segments: path.iter().map(path_seg).collect(), span }; + let parent_scope = + ParentScope { module: self.current_module, ..self.dummy_parent_scope() }; + if let Ok((_, res)) = + self.resolve_macro_path(&path, None, &parent_scope, false, false) { + return Some(PartialRes::new(res)); + } } + fin_res } @@ -4124,195 +4271,6 @@ impl<'a> Resolver<'a> { res } - fn lookup_assoc_candidate bool>( - &mut self, - ident: Ident, - ns: Namespace, - filter_fn: FilterFn, - ) -> Option { - fn extract_node_id(t: &Ty) -> Option { - match t.node { - TyKind::Path(None, _) => Some(t.id), - TyKind::Rptr(_, ref mut_ty) => extract_node_id(&mut_ty.ty), - // This doesn't handle the remaining `Ty` variants as they are not - // that commonly the self_type, it might be interesting to provide - // support for those in future. - _ => None, - } - } - - // Fields are generally expected in the same contexts as locals. - if filter_fn(Res::Local(ast::DUMMY_NODE_ID)) { - if let Some(node_id) = self.current_self_type.as_ref().and_then(extract_node_id) { - // Look for a field with the same name in the current self_type. - if let Some(resolution) = self.partial_res_map.get(&node_id) { - match resolution.base_res() { - Res::Def(DefKind::Struct, did) | Res::Def(DefKind::Union, did) - if resolution.unresolved_segments() == 0 => { - if let Some(field_names) = self.field_names.get(&did) { - if field_names.iter().any(|&field_name| ident.name == field_name) { - return Some(AssocSuggestion::Field); - } - } - } - _ => {} - } - } - } - } - - for assoc_type_ident in &self.current_trait_assoc_types { - if *assoc_type_ident == ident { - return Some(AssocSuggestion::AssocItem); - } - } - - // Look for associated items in the current trait. - if let Some((module, _)) = self.current_trait_ref { - if let Ok(binding) = self.resolve_ident_in_module( - ModuleOrUniformRoot::Module(module), - ident, - ns, - None, - false, - module.span, - ) { - let res = binding.res(); - if filter_fn(res) { - debug!("extract_node_id res not filtered"); - return Some(if self.has_self.contains(&res.def_id()) { - AssocSuggestion::MethodWithSelf - } else { - AssocSuggestion::AssocItem - }); - } - } - } - - None - } - - fn lookup_typo_candidate( - &mut self, - path: &[Segment], - ns: Namespace, - filter_fn: FilterFn, - span: Span, - ) -> Option - where - FilterFn: Fn(Res) -> bool, - { - let add_module_candidates = |module: Module<'_>, names: &mut Vec| { - for (&(ident, _), resolution) in module.resolutions.borrow().iter() { - if let Some(binding) = resolution.borrow().binding { - if filter_fn(binding.res()) { - names.push(TypoSuggestion { - candidate: ident.name, - article: binding.res().article(), - kind: binding.res().descr(), - }); - } - } - } - }; - - let mut names = Vec::new(); - if path.len() == 1 { - // Search in lexical scope. - // Walk backwards up the ribs in scope and collect candidates. - for rib in self.ribs[ns].iter().rev() { - // Locals and type parameters - for (ident, &res) in &rib.bindings { - if filter_fn(res) { - names.push(TypoSuggestion { - candidate: ident.name, - article: res.article(), - kind: res.descr(), - }); - } - } - // Items in scope - if let ModuleRibKind(module) = rib.kind { - // Items from this module - add_module_candidates(module, &mut names); - - if let ModuleKind::Block(..) = module.kind { - // We can see through blocks - } else { - // Items from the prelude - if !module.no_implicit_prelude { - names.extend(self.extern_prelude.clone().iter().flat_map(|(ident, _)| { - self.crate_loader - .maybe_process_path_extern(ident.name, ident.span) - .and_then(|crate_id| { - let crate_mod = Res::Def( - DefKind::Mod, - DefId { - krate: crate_id, - index: CRATE_DEF_INDEX, - }, - ); - - if filter_fn(crate_mod) { - Some(TypoSuggestion { - candidate: ident.name, - article: "a", - kind: "crate", - }) - } else { - None - } - }) - })); - - if let Some(prelude) = self.prelude { - add_module_candidates(prelude, &mut names); - } - } - break; - } - } - } - // Add primitive types to the mix - if filter_fn(Res::PrimTy(Bool)) { - names.extend( - self.primitive_type_table.primitive_types.iter().map(|(name, _)| { - TypoSuggestion { - candidate: *name, - article: "a", - kind: "primitive type", - } - }) - ) - } - } else { - // Search in module. - let mod_path = &path[..path.len() - 1]; - if let PathResult::Module(module) = self.resolve_path_without_parent_scope( - mod_path, Some(TypeNS), false, span, CrateLint::No - ) { - if let ModuleOrUniformRoot::Module(module) = module { - add_module_candidates(module, &mut names); - } - } - } - - let name = path[path.len() - 1].ident.name; - // Make sure error reporting is deterministic. - names.sort_by_cached_key(|suggestion| suggestion.candidate.as_str()); - - match find_best_match_for_name( - names.iter().map(|suggestion| &suggestion.candidate), - &name.as_str(), - None, - ) { - Some(found) if found != name => names - .into_iter() - .find(|suggestion| suggestion.candidate == found), - _ => None, - } - } - fn with_resolved_label(&mut self, label: Option