about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustc_resolve/error_reporting.rs313
-rw-r--r--src/librustc_resolve/lib.rs98
-rw-r--r--src/librustc_resolve/resolve_imports.rs59
-rw-r--r--src/test/ui/auxiliary/issue-59764.rs18
-rw-r--r--src/test/ui/issue-59764.rs136
-rw-r--r--src/test/ui/issue-59764.stderr241
6 files changed, 751 insertions, 114 deletions
diff --git a/src/librustc_resolve/error_reporting.rs b/src/librustc_resolve/error_reporting.rs
index 461d02e515d..931bce91d7d 100644
--- a/src/librustc_resolve/error_reporting.rs
+++ b/src/librustc_resolve/error_reporting.rs
@@ -4,16 +4,17 @@ use errors::{Applicability, DiagnosticBuilder, DiagnosticId};
 use log::debug;
 use rustc::hir::def::{Def, CtorKind, Namespace::*};
 use rustc::hir::def_id::{CRATE_DEF_INDEX, DefId};
-use rustc::session::config::nightly_options;
-use syntax::ast::{Expr, ExprKind};
-use syntax::symbol::keywords;
-use syntax_pos::Span;
+use rustc::session::{Session, config::nightly_options};
+use syntax::ast::{Expr, ExprKind, Ident};
+use syntax::ext::base::MacroKind;
+use syntax::symbol::{Symbol, keywords};
+use syntax_pos::{BytePos, Span};
 
 use crate::macros::ParentScope;
-use crate::resolve_imports::ImportResolver;
+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};
+            PathSource, Resolver, Segment, Suggestion};
 
 impl<'a> Resolver<'a> {
     /// Handles error reporting for `smart_resolve_path_fragment` function.
@@ -428,7 +429,7 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
         span: Span,
         mut path: Vec<Segment>,
         parent_scope: &ParentScope<'b>,
-    ) -> Option<(Vec<Segment>, Option<String>)> {
+    ) -> Option<(Vec<Segment>, Vec<String>)> {
         debug!("make_path_suggestion: span={:?} path={:?}", span, path);
 
         match (path.get(0), path.get(1)) {
@@ -463,13 +464,13 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
         span: Span,
         mut path: Vec<Segment>,
         parent_scope: &ParentScope<'b>,
-    ) -> Option<(Vec<Segment>, Option<String>)> {
+    ) -> Option<(Vec<Segment>, Vec<String>)> {
         // Replace first ident with `self` and check if that is valid.
         path[0].ident.name = keywords::SelfLower.name();
         let result = self.resolve_path(&path, None, parent_scope, false, span, CrateLint::No);
         debug!("make_missing_self_suggestion: path={:?} result={:?}", path, result);
         if let PathResult::Module(..) = result {
-            Some((path, None))
+            Some((path, Vec::new()))
         } else {
             None
         }
@@ -487,7 +488,7 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
         span: Span,
         mut path: Vec<Segment>,
         parent_scope: &ParentScope<'b>,
-    ) -> Option<(Vec<Segment>, Option<String>)> {
+    ) -> Option<(Vec<Segment>, Vec<String>)> {
         // Replace first ident with `crate` and check if that is valid.
         path[0].ident.name = keywords::Crate.name();
         let result = self.resolve_path(&path, None, parent_scope, false, span, CrateLint::No);
@@ -495,11 +496,11 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
         if let PathResult::Module(..) = result {
             Some((
                 path,
-                Some(
+                vec![
                     "`use` statements changed in Rust 2018; read more at \
                      <https://doc.rust-lang.org/edition-guide/rust-2018/module-system/path-\
                      clarity.html>".to_string()
-                ),
+                ],
             ))
         } else {
             None
@@ -518,13 +519,13 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
         span: Span,
         mut path: Vec<Segment>,
         parent_scope: &ParentScope<'b>,
-    ) -> Option<(Vec<Segment>, Option<String>)> {
+    ) -> Option<(Vec<Segment>, Vec<String>)> {
         // Replace first ident with `crate` and check if that is valid.
         path[0].ident.name = keywords::Super.name();
         let result = self.resolve_path(&path, None, parent_scope, false, span, CrateLint::No);
         debug!("make_missing_super_suggestion:  path={:?} result={:?}", path, result);
         if let PathResult::Module(..) = result {
-            Some((path, None))
+            Some((path, Vec::new()))
         } else {
             None
         }
@@ -545,7 +546,7 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
         span: Span,
         mut path: Vec<Segment>,
         parent_scope: &ParentScope<'b>,
-    ) -> Option<(Vec<Segment>, Option<String>)> {
+    ) -> Option<(Vec<Segment>, Vec<String>)> {
         if path[1].ident.span.rust_2015() {
             return None;
         }
@@ -564,10 +565,290 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
             debug!("make_external_crate_suggestion: name={:?} path={:?} result={:?}",
                     name, path, result);
             if let PathResult::Module(..) = result {
-                return Some((path, None));
+                return Some((path, Vec::new()));
             }
         }
 
         None
     }
+
+    /// Suggests importing a macro from the root of the crate rather than a module within
+    /// the crate.
+    ///
+    /// ```
+    /// help: a macro with this name exists at the root of the crate
+    ///    |
+    /// LL | use issue_59764::makro;
+    ///    |     ^^^^^^^^^^^^^^^^^^
+    ///    |
+    ///    = note: this could be because a macro annotated with `#[macro_export]` will be exported
+    ///            at the root of the crate instead of the module where it is defined
+    /// ```
+    pub(crate) fn check_for_module_export_macro(
+        &self,
+        directive: &'b ImportDirective<'b>,
+        module: ModuleOrUniformRoot<'b>,
+        ident: Ident,
+    ) -> Option<(Option<Suggestion>, Vec<String>)> {
+        let mut crate_module = if let ModuleOrUniformRoot::Module(module) = module {
+            module
+        } else {
+            return None;
+        };
+
+        while let Some(parent) = crate_module.parent {
+            crate_module = parent;
+        }
+
+        if ModuleOrUniformRoot::same_def(ModuleOrUniformRoot::Module(crate_module), module) {
+            // Don't make a suggestion if the import was already from the root of the
+            // crate.
+            return None;
+        }
+
+        let resolutions = crate_module.resolutions.borrow();
+        let resolution = resolutions.get(&(ident, MacroNS))?;
+        let binding = resolution.borrow().binding()?;
+        if let Def::Macro(_, MacroKind::Bang) = binding.def() {
+            let module_name = crate_module.kind.name().unwrap();
+            let import = match directive.subclass {
+                ImportDirectiveSubclass::SingleImport { source, target, .. } if source != target =>
+                    format!("{} as {}", source, target),
+                _ => format!("{}", ident),
+            };
+
+            let mut corrections: Vec<(Span, String)> = Vec::new();
+            if !directive.is_nested() {
+                // Assume this is the easy case of `use issue_59764::foo::makro;` and just remove
+                // intermediate segments.
+                corrections.push((directive.span, format!("{}::{}", module_name, import)));
+            } else {
+                // Find the binding span (and any trailing commas and spaces).
+                //   ie. `use a::b::{c, d, e};`
+                //                      ^^^
+                let (found_closing_brace, binding_span) = find_span_of_binding_until_next_binding(
+                    self.resolver.session, directive.span, directive.use_span,
+                );
+                debug!("check_for_module_export_macro: found_closing_brace={:?} binding_span={:?}",
+                       found_closing_brace, binding_span);
+
+                let mut removal_span = binding_span;
+                if found_closing_brace {
+                    // If the binding span ended with a closing brace, as in the below example:
+                    //   ie. `use a::b::{c, d};`
+                    //                      ^
+                    // Then expand the span of characters to remove to include the previous
+                    // binding's trailing comma.
+                    //   ie. `use a::b::{c, d};`
+                    //                    ^^^
+                    if let Some(previous_span) = extend_span_to_previous_binding(
+                        self.resolver.session, binding_span,
+                    ) {
+                        debug!("check_for_module_export_macro: previous_span={:?}", previous_span);
+                        removal_span = removal_span.with_lo(previous_span.lo());
+                    }
+                }
+                debug!("check_for_module_export_macro: removal_span={:?}", removal_span);
+
+                // Remove the `removal_span`.
+                corrections.push((removal_span, "".to_string()));
+
+                // Find the span after the crate name and if it has nested imports immediatately
+                // after the crate name already.
+                //   ie. `use a::b::{c, d};`
+                //               ^^^^^^^^^
+                //   or  `use a::{b, c, d}};`
+                //               ^^^^^^^^^^^
+                let (has_nested, after_crate_name) = find_span_immediately_after_crate_name(
+                    self.resolver.session, module_name, directive.use_span,
+                );
+                debug!("check_for_module_export_macro: has_nested={:?} after_crate_name={:?}",
+                       has_nested, after_crate_name);
+
+                let source_map = self.resolver.session.source_map();
+
+                // Add the import to the start, with a `{` if required.
+                let start_point = source_map.start_point(after_crate_name);
+                if let Ok(start_snippet) = source_map.span_to_snippet(start_point) {
+                    corrections.push((
+                        start_point,
+                        if has_nested {
+                            // In this case, `start_snippet` must equal '{'.
+                            format!("{}{}, ", start_snippet, import)
+                        } else {
+                            // In this case, add a `{`, then the moved import, then whatever
+                            // was there before.
+                            format!("{{{}, {}", import, start_snippet)
+                        }
+                    ));
+                }
+
+                // Add a `};` to the end if nested, matching the `{` added at the start.
+                if !has_nested {
+                    corrections.push((source_map.end_point(after_crate_name),
+                                     "};".to_string()));
+                }
+            }
+
+            let suggestion = Some((
+                corrections,
+                String::from("a macro with this name exists at the root of the crate"),
+                Applicability::MaybeIncorrect,
+            ));
+            let note = vec![
+                "this could be because a macro annotated with `#[macro_export]` will be exported \
+                 at the root of the crate instead of the module where it is defined".to_string(),
+            ];
+            Some((suggestion, note))
+        } else {
+            None
+        }
+    }
+}
+
+/// Given a `binding_span` of a binding within a use statement:
+///
+/// ```
+/// use foo::{a, b, c};
+///              ^
+/// ```
+///
+/// then return the span until the next binding or the end of the statement:
+///
+/// ```
+/// use foo::{a, b, c};
+///              ^^^
+/// ```
+pub(crate) fn find_span_of_binding_until_next_binding(
+    sess: &Session,
+    binding_span: Span,
+    use_span: Span,
+) -> (bool, Span) {
+    let source_map = sess.source_map();
+
+    // Find the span of everything after the binding.
+    //   ie. `a, e};` or `a};`
+    let binding_until_end = binding_span.with_hi(use_span.hi());
+
+    // Find everything after the binding but not including the binding.
+    //   ie. `, e};` or `};`
+    let after_binding_until_end = binding_until_end.with_lo(binding_span.hi());
+
+    // Keep characters in the span until we encounter something that isn't a comma or
+    // whitespace.
+    //   ie. `, ` or ``.
+    //
+    // Also note whether a closing brace character was encountered. If there
+    // was, then later go backwards to remove any trailing commas that are left.
+    let mut found_closing_brace = false;
+    let after_binding_until_next_binding = source_map.span_take_while(
+        after_binding_until_end,
+        |&ch| {
+            if ch == '}' { found_closing_brace = true; }
+            ch == ' ' || ch == ','
+        }
+    );
+
+    // Combine the two spans.
+    //   ie. `a, ` or `a`.
+    //
+    // Removing these would leave `issue_52891::{d, e};` or `issue_52891::{d, e, };`
+    let span = binding_span.with_hi(after_binding_until_next_binding.hi());
+
+    (found_closing_brace, span)
+}
+
+/// Given a `binding_span`, return the span through to the comma or opening brace of the previous
+/// binding.
+///
+/// ```
+/// use foo::a::{a, b, c};
+///               ^^--- binding span
+///               |
+///               returned span
+///
+/// use foo::{a, b, c};
+///           --- binding span
+/// ```
+pub(crate) fn extend_span_to_previous_binding(
+    sess: &Session,
+    binding_span: Span,
+) -> Option<Span> {
+    let source_map = sess.source_map();
+
+    // `prev_source` will contain all of the source that came before the span.
+    // Then split based on a command and take the first (ie. closest to our span)
+    // snippet. In the example, this is a space.
+    let prev_source = source_map.span_to_prev_source(binding_span).ok()?;
+
+    let prev_comma = prev_source.rsplit(',').collect::<Vec<_>>();
+    let prev_starting_brace = prev_source.rsplit('{').collect::<Vec<_>>();
+    if prev_comma.len() <= 1 || prev_starting_brace.len() <= 1 {
+        return None;
+    }
+
+    let prev_comma = prev_comma.first().unwrap();
+    let prev_starting_brace = prev_starting_brace.first().unwrap();
+
+    // If the amount of source code before the comma is greater than
+    // the amount of source code before the starting brace then we've only
+    // got one item in the nested item (eg. `issue_52891::{self}`).
+    if prev_comma.len() > prev_starting_brace.len() {
+        return None;
+    }
+
+    Some(binding_span.with_lo(BytePos(
+        // Take away the number of bytes for the characters we've found and an
+        // extra for the comma.
+        binding_span.lo().0 - (prev_comma.as_bytes().len() as u32) - 1
+    )))
+}
+
+/// Given a `use_span` of a binding within a use statement, returns the highlighted span and if
+/// it is a nested use tree.
+///
+/// ```
+/// use foo::a::{b, c};
+///          ^^^^^^^^^^ // false
+///
+/// use foo::{a, b, c};
+///          ^^^^^^^^^^ // true
+///
+/// use foo::{a, b::{c, d}};
+///          ^^^^^^^^^^^^^^^ // true
+/// ```
+fn find_span_immediately_after_crate_name(
+    sess: &Session,
+    module_name: Symbol,
+    use_span: Span,
+) -> (bool, Span) {
+    debug!("find_span_immediately_after_crate_name: module_name={:?} use_span={:?}",
+           module_name, use_span);
+    let source_map = sess.source_map();
+
+    // Using `use issue_59764::foo::{baz, makro};` as an example throughout..
+    let mut num_colons = 0;
+    // Find second colon.. `use issue_59764:`
+    let until_second_colon = source_map.span_take_while(use_span, |c| {
+        if *c == ':' { num_colons += 1; }
+        match c {
+            ':' if num_colons == 2 => false,
+            _ => true,
+        }
+    });
+    // Find everything after the second colon.. `foo::{baz, makro};`
+    let from_second_colon = use_span.with_lo(until_second_colon.hi() + BytePos(1));
+
+    let mut found_a_non_whitespace_character = false;
+    // Find the first non-whitespace character in `from_second_colon`.. `f`
+    let after_second_colon = source_map.span_take_while(from_second_colon, |c| {
+        if found_a_non_whitespace_character { return false; }
+        if !c.is_whitespace() { found_a_non_whitespace_character = true; }
+        true
+    });
+
+    // Find the first `{` in from_second_colon.. `foo::{`
+    let next_left_bracket = source_map.span_through_char(from_second_colon, '{');
+
+    (next_left_bracket == after_second_colon, from_second_colon)
 }
diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs
index ffc783ae9f2..b34786d8f12 100644
--- a/src/librustc_resolve/lib.rs
+++ b/src/librustc_resolve/lib.rs
@@ -50,7 +50,7 @@ use syntax::ast::{QSelf, TraitItemKind, TraitRef, Ty, TyKind};
 use syntax::ptr::P;
 use syntax::{span_err, struct_span_err, unwrap_or, walk_list};
 
-use syntax_pos::{BytePos, Span, DUMMY_SP, MultiSpan};
+use syntax_pos::{Span, DUMMY_SP, MultiSpan};
 use errors::{Applicability, DiagnosticBuilder, DiagnosticId};
 
 use log::debug;
@@ -62,6 +62,7 @@ use std::mem::replace;
 use rustc_data_structures::ptr_key::PtrKey;
 use rustc_data_structures::sync::Lrc;
 
+use error_reporting::{find_span_of_binding_until_next_binding, extend_span_to_previous_binding};
 use resolve_imports::{ImportDirective, ImportDirectiveSubclass, NameResolution, ImportResolver};
 use macros::{InvocationData, LegacyBinding, ParentScope};
 
@@ -138,8 +139,8 @@ impl Ord for BindingError {
     }
 }
 
-/// A span, message, replacement text, and applicability.
-type Suggestion = (Span, String, String, Applicability);
+/// 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.
@@ -389,8 +390,8 @@ fn resolve_struct_error<'sess, 'a>(resolver: &'sess Resolver<'_>,
                                            "failed to resolve: {}", &label);
             err.span_label(span, label);
 
-            if let Some((span, msg, suggestion, applicability)) = suggestion {
-                err.span_suggestion(span, &msg, suggestion, applicability);
+            if let Some((suggestions, msg, applicability)) = suggestion {
+                err.multipart_suggestion(&msg, suggestions, applicability);
             }
 
             err
@@ -1091,6 +1092,16 @@ enum ModuleKind {
     Def(Def, Name),
 }
 
+impl ModuleKind {
+    /// Get name of the module.
+    pub fn name(&self) -> Option<Name> {
+        match self {
+            ModuleKind::Block(..) => None,
+            ModuleKind::Def(_, name) => Some(*name),
+        }
+    }
+}
+
 /// One node in the tree of modules.
 pub struct ModuleData<'a> {
     parent: Option<Module<'a>>,
@@ -3770,9 +3781,8 @@ impl<'a> Resolver<'a> {
                             (
                                 String::from("unresolved import"),
                                 Some((
-                                    ident.span,
+                                    vec![(ident.span, candidate.path.to_string())],
                                     String::from("a similar path exists"),
-                                    candidate.path.to_string(),
                                     Applicability::MaybeIncorrect,
                                 )),
                             )
@@ -5141,7 +5151,6 @@ impl<'a> Resolver<'a> {
     ) {
         assert!(directive.is_nested());
         let message = "remove unnecessary import";
-        let source_map = self.session.source_map();
 
         // Two examples will be used to illustrate the span manipulations we're doing:
         //
@@ -5150,73 +5159,24 @@ impl<'a> Resolver<'a> {
         // - Given `use issue_52891::{d, e, a};` where `a` is a duplicate then `binding_span` is
         //   `a` and `directive.use_span` is `issue_52891::{d, e, a};`.
 
-        // Find the span of everything after the binding.
-        //   ie. `a, e};` or `a};`
-        let binding_until_end = binding_span.with_hi(directive.use_span.hi());
-
-        // Find everything after the binding but not including the binding.
-        //   ie. `, e};` or `};`
-        let after_binding_until_end = binding_until_end.with_lo(binding_span.hi());
-
-        // Keep characters in the span until we encounter something that isn't a comma or
-        // whitespace.
-        //   ie. `, ` or ``.
-        //
-        // Also note whether a closing brace character was encountered. If there
-        // was, then later go backwards to remove any trailing commas that are left.
-        let mut found_closing_brace = false;
-        let after_binding_until_next_binding = source_map.span_take_while(
-            after_binding_until_end,
-            |&ch| {
-                if ch == '}' { found_closing_brace = true; }
-                ch == ' ' || ch == ','
-            }
+        let (found_closing_brace, span) = find_span_of_binding_until_next_binding(
+            self.session, binding_span, directive.use_span,
         );
 
-        // Combine the two spans.
-        //   ie. `a, ` or `a`.
-        //
-        // Removing these would leave `issue_52891::{d, e};` or `issue_52891::{d, e, };`
-        let span = binding_span.with_hi(after_binding_until_next_binding.hi());
-
         // If there was a closing brace then identify the span to remove any trailing commas from
         // previous imports.
         if found_closing_brace {
-            if let Ok(prev_source) = source_map.span_to_prev_source(span) {
-                // `prev_source` will contain all of the source that came before the span.
-                // Then split based on a command and take the first (ie. closest to our span)
-                // snippet. In the example, this is a space.
-                let prev_comma = prev_source.rsplit(',').collect::<Vec<_>>();
-                let prev_starting_brace = prev_source.rsplit('{').collect::<Vec<_>>();
-                if prev_comma.len() > 1 && prev_starting_brace.len() > 1 {
-                    let prev_comma = prev_comma.first().unwrap();
-                    let prev_starting_brace = prev_starting_brace.first().unwrap();
-
-                    // If the amount of source code before the comma is greater than
-                    // the amount of source code before the starting brace then we've only
-                    // got one item in the nested item (eg. `issue_52891::{self}`).
-                    if prev_comma.len() > prev_starting_brace.len() {
-                        // So just remove the entire line...
-                        err.span_suggestion(
-                            directive.use_span_with_attributes,
-                            message,
-                            String::new(),
-                            Applicability::MaybeIncorrect,
-                        );
-                        return;
-                    }
-
-                    let span = span.with_lo(BytePos(
-                        // Take away the number of bytes for the characters we've found and an
-                        // extra for the comma.
-                        span.lo().0 - (prev_comma.as_bytes().len() as u32) - 1
-                    ));
-                    err.tool_only_span_suggestion(
-                        span, message, String::new(), Applicability::MaybeIncorrect,
-                    );
-                    return;
-                }
+            if let Some(span) = extend_span_to_previous_binding(self.session, span) {
+                err.tool_only_span_suggestion(span, message, String::new(),
+                                              Applicability::MaybeIncorrect);
+            } else {
+                // Remove the entire line if we cannot extend the span back, this indicates a
+                // `issue_52891::{self}` case.
+                err.span_suggestion(directive.use_span_with_attributes, message, String::new(),
+                                    Applicability::MaybeIncorrect);
             }
+
+            return;
         }
 
         err.span_suggestion(span, message, String::new(), Applicability::MachineApplicable);
diff --git a/src/librustc_resolve/resolve_imports.rs b/src/librustc_resolve/resolve_imports.rs
index 97b630ba5f2..62af6e19603 100644
--- a/src/librustc_resolve/resolve_imports.rs
+++ b/src/librustc_resolve/resolve_imports.rs
@@ -145,7 +145,7 @@ pub struct NameResolution<'a> {
 
 impl<'a> NameResolution<'a> {
     // Returns the binding for the name if it is known or None if it not known.
-    fn binding(&self) -> Option<&'a NameBinding<'a>> {
+    pub(crate) fn binding(&self) -> Option<&'a NameBinding<'a>> {
         self.binding.and_then(|binding| {
             if !binding.is_glob_import() ||
                self.single_imports.is_empty() { Some(binding) } else { None }
@@ -636,7 +636,7 @@ impl<'a> Resolver<'a> {
 struct UnresolvedImportError {
     span: Span,
     label: Option<String>,
-    note: Option<String>,
+    note: Vec<String>,
     suggestion: Option<Suggestion>,
 }
 
@@ -756,8 +756,8 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
         /// Upper limit on the number of `span_label` messages.
         const MAX_LABEL_COUNT: usize = 10;
 
-        let (span, msg, note) = if errors.is_empty() {
-            (span.unwrap(), "unresolved import".to_string(), None)
+        let (span, msg) = if errors.is_empty() {
+            (span.unwrap(), "unresolved import".to_string())
         } else {
             let span = MultiSpan::from_spans(
                 errors
@@ -766,11 +766,6 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
                     .collect(),
             );
 
-            let note = errors
-                .iter()
-                .filter_map(|(_, err)| err.note.as_ref())
-                .last();
-
             let paths = errors
                 .iter()
                 .map(|(path, _)| format!("`{}`", path))
@@ -782,13 +777,15 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
                 paths.join(", "),
             );
 
-            (span, msg, note)
+            (span, msg)
         };
 
         let mut diag = struct_span_err!(self.resolver.session, span, E0432, "{}", &msg);
 
-        if let Some(note) = &note {
-            diag.note(note);
+        if let Some((_, UnresolvedImportError { note, .. })) = errors.iter().last() {
+            for message in note {
+                diag.note(&message);
+            }
         }
 
         for (_, err) in errors.into_iter().take(MAX_LABEL_COUNT) {
@@ -796,8 +793,8 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
                 diag.span_label(err.span, label);
             }
 
-            if let Some((span, msg, suggestion, applicability)) = err.suggestion {
-                diag.span_suggestion(span, &msg, suggestion, applicability);
+            if let Some((suggestions, msg, applicability)) = err.suggestion {
+                diag.multipart_suggestion(&msg, suggestions, applicability);
             }
         }
 
@@ -950,9 +947,8 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
                                 label: None,
                                 note,
                                 suggestion: Some((
-                                    span,
+                                    vec![(span, Segment::names_to_string(&suggestion))],
                                     String::from("a similar path exists"),
-                                    Segment::names_to_string(&suggestion),
                                     Applicability::MaybeIncorrect,
                                 )),
                             }
@@ -961,7 +957,7 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
                             UnresolvedImportError {
                                 span,
                                 label: Some(label),
-                                note: None,
+                                note: Vec::new(),
                                 suggestion,
                             }
                         }
@@ -1006,7 +1002,7 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
                         return Some(UnresolvedImportError {
                             span: directive.span,
                             label: Some(String::from("cannot glob-import a module into itself")),
-                            note: None,
+                            note: Vec::new(),
                             suggestion: None,
                         });
                     }
@@ -1114,15 +1110,19 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
                     }
                 });
 
-                let lev_suggestion =
-                    find_best_match_for_name(names, &ident.as_str(), None).map(|suggestion| {
-                        (
-                            ident.span,
-                            String::from("a similar name exists in the module"),
-                            suggestion.to_string(),
-                            Applicability::MaybeIncorrect,
-                        )
-                    });
+                let lev_suggestion = find_best_match_for_name(names, &ident.as_str(), None)
+                   .map(|suggestion|
+                        (vec![(ident.span, suggestion.to_string())],
+                         String::from("a similar name exists in the module"),
+                         Applicability::MaybeIncorrect)
+                    );
+
+                let (suggestion, note) = match self.check_for_module_export_macro(
+                    directive, module, ident,
+                ) {
+                    Some((suggestion, note)) => (suggestion.or(lev_suggestion), note),
+                    _ => (lev_suggestion, Vec::new()),
+                };
 
                 let label = match module {
                     ModuleOrUniformRoot::Module(module) => {
@@ -1143,11 +1143,12 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
                         }
                     }
                 };
+
                 Some(UnresolvedImportError {
                     span: directive.span,
                     label: Some(label),
-                    note: None,
-                    suggestion: lev_suggestion,
+                    note,
+                    suggestion,
                 })
             } else {
                 // `resolve_ident_in_module` reported a privacy error.
diff --git a/src/test/ui/auxiliary/issue-59764.rs b/src/test/ui/auxiliary/issue-59764.rs
new file mode 100644
index 00000000000..a92eed968d0
--- /dev/null
+++ b/src/test/ui/auxiliary/issue-59764.rs
@@ -0,0 +1,18 @@
+pub mod foo {
+    #[macro_export]
+    macro_rules! makro {
+        ($foo:ident) => {
+            fn $foo() { }
+        }
+    }
+
+    pub fn baz() {}
+
+    pub fn foobar() {}
+
+    pub mod barbaz {
+        pub fn barfoo() {}
+    }
+}
+
+pub fn foobaz() {}
diff --git a/src/test/ui/issue-59764.rs b/src/test/ui/issue-59764.rs
new file mode 100644
index 00000000000..09dee8c2732
--- /dev/null
+++ b/src/test/ui/issue-59764.rs
@@ -0,0 +1,136 @@
+// aux-build:issue-59764.rs
+// compile-flags:--extern issue_59764
+// edition:2018
+
+#![allow(warnings)]
+
+// This tests the suggestion to import macros from the root of a crate. This aims to capture
+// the case where a user attempts to import a macro from the definition location instead of the
+// root of the crate and the macro is annotated with `#![macro_export]`.
+
+// Edge cases..
+
+mod multiple_imports_same_line_at_end {
+    use issue_59764::foo::{baz, makro};
+    //~^ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+}
+
+mod multiple_imports_multiline_at_end_trailing_comma {
+    use issue_59764::foo::{
+        baz,
+        makro, //~ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+    };
+}
+
+mod multiple_imports_multiline_at_end {
+    use issue_59764::foo::{
+        baz,
+        makro //~ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+    };
+}
+
+mod multiple_imports_same_line_in_middle {
+    use issue_59764::foo::{baz, makro, foobar};
+    //~^ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+}
+
+mod multiple_imports_multiline_in_middle_trailing_comma {
+    use issue_59764::foo::{
+        baz,
+        makro, //~ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+        foobar,
+    };
+}
+
+mod multiple_imports_multiline_in_middle {
+    use issue_59764::foo::{
+        baz,
+        makro, //~ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+        foobar
+    };
+}
+
+mod nested_imports {
+    use issue_59764::{foobaz, foo::makro};
+    //~^ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+}
+
+mod nested_multiple_imports {
+    use issue_59764::{foobaz, foo::{baz, makro}};
+    //~^ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+}
+
+mod nested_multiline_multiple_imports_trailing_comma {
+    use issue_59764::{
+        foobaz,
+        foo::{
+            baz,
+            makro, //~ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+        },
+    };
+}
+
+mod nested_multiline_multiple_imports {
+    use issue_59764::{
+        foobaz,
+        foo::{
+            baz,
+            makro //~ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+        }
+    };
+}
+
+mod doubly_nested_multiple_imports {
+    use issue_59764::{foobaz, foo::{baz, makro, barbaz::{barfoo}}};
+    //~^ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+}
+
+mod doubly_multiline_nested_multiple_imports {
+    use issue_59764::{
+        foobaz,
+        foo::{
+            baz,
+            makro, //~ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+            barbaz::{
+                barfoo,
+            }
+        }
+    };
+}
+
+mod renamed_import {
+    use issue_59764::foo::makro as baz;
+    //~^ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+}
+
+mod renamed_multiple_imports {
+    use issue_59764::foo::{baz, makro as foobar};
+    //~^ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+}
+
+mod lots_of_whitespace {
+    use
+        issue_59764::{
+
+            foobaz,
+
+
+            foo::{baz,
+
+                makro as foobar} //~ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+
+        };
+}
+
+// Simple case..
+
+use issue_59764::foo::makro;
+//~^ ERROR unresolved import `issue_59764::foo::makro` [E0432]
+
+makro!(bar);
+//~^ ERROR cannot determine resolution for the macro `makro`
+
+fn main() {
+    bar();
+    //~^ ERROR cannot find function `bar` in this scope [E0425]
+}
diff --git a/src/test/ui/issue-59764.stderr b/src/test/ui/issue-59764.stderr
new file mode 100644
index 00000000000..924e69f5f97
--- /dev/null
+++ b/src/test/ui/issue-59764.stderr
@@ -0,0 +1,241 @@
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:14:33
+   |
+LL |     use issue_59764::foo::{baz, makro};
+   |                                 ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, foo::{baz}};
+   |                      ^^^^^^^^^       --^^
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:21:9
+   |
+LL |         makro,
+   |         ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, foo::{
+LL |         baz,
+LL |
+LL |     }};
+   |
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:28:9
+   |
+LL |         makro
+   |         ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, foo::{
+LL |         baz,
+LL |
+LL |     }};
+   |
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:33:33
+   |
+LL |     use issue_59764::foo::{baz, makro, foobar};
+   |                                 ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, foo::{baz, foobar}};
+   |                      ^^^^^^^^^         --      ^^
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:40:9
+   |
+LL |         makro,
+   |         ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, foo::{
+LL |         baz,
+LL |
+LL |         foobar,
+LL |     }};
+   |
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:48:9
+   |
+LL |         makro,
+   |         ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, foo::{
+LL |         baz,
+LL |
+LL |         foobar
+LL |     }};
+   |
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:54:31
+   |
+LL |     use issue_59764::{foobaz, foo::makro};
+   |                               ^^^^^^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, foobaz};
+   |                      ^^^^^^^      --
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:59:42
+   |
+LL |     use issue_59764::{foobaz, foo::{baz, makro}};
+   |                                          ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, foobaz, foo::{baz}};
+   |                      ^^^^^^^                 --
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:68:13
+   |
+LL |             makro,
+   |             ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, 
+LL |         foobaz,
+LL |         foo::{
+LL |             baz,
+LL |
+   |
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:78:13
+   |
+LL |             makro
+   |             ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, 
+LL |         foobaz,
+LL |         foo::{
+LL |             baz,
+LL |
+   |
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:84:42
+   |
+LL |     use issue_59764::{foobaz, foo::{baz, makro, barbaz::{barfoo}}};
+   |                                          ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, foobaz, foo::{baz, barbaz::{barfoo}}};
+   |                      ^^^^^^^                   --
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:93:13
+   |
+LL |             makro,
+   |             ^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro, 
+LL |         foobaz,
+LL |         foo::{
+LL |             baz,
+LL |
+   |
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:102:9
+   |
+LL |     use issue_59764::foo::makro as baz;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::makro as baz;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:107:33
+   |
+LL |     use issue_59764::foo::{baz, makro as foobar};
+   |                                 ^^^^^^^^^^^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |     use issue_59764::{makro as foobar, foo::{baz}};
+   |                      ^^^^^^^^^^^^^^^^^^^       --^^
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:120:17
+   |
+LL |                 makro as foobar}
+   |                 ^^^^^^^^^^^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL |         issue_59764::{makro as foobar, 
+LL | 
+LL |             foobaz,
+LL | 
+LL | 
+LL |             foo::{baz}
+   |
+
+error[E0432]: unresolved import `issue_59764::foo::makro`
+  --> $DIR/issue-59764.rs:127:5
+   |
+LL | use issue_59764::foo::makro;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^ no `makro` in `foo`
+   |
+   = note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
+help: a macro with this name exists at the root of the crate
+   |
+LL | use issue_59764::makro;
+   |     ^^^^^^^^^^^^^^^^^^
+
+error: cannot determine resolution for the macro `makro`
+  --> $DIR/issue-59764.rs:130:1
+   |
+LL | makro!(bar);
+   | ^^^^^
+   |
+   = note: import resolution is stuck, try simplifying macro imports
+
+error[E0425]: cannot find function `bar` in this scope
+  --> $DIR/issue-59764.rs:134:5
+   |
+LL |     bar();
+   |     ^^^ not found in this scope
+
+error: aborting due to 18 previous errors
+
+Some errors occurred: E0425, E0432.
+For more information about an error, try `rustc --explain E0425`.