From d37da1e332a77a4cd66c2f36b4a5457f40a7bfd5 Mon Sep 17 00:00:00 2001
From: "Felix S. Klock II" <pnkfelix@pnkfx.org>
Date: Thu, 20 Jan 2022 14:07:54 -0500
Subject: [PATCH] Adjusted diagnostic output so that if there is no `use` in a
 item sequence, then we just suggest the first legal position where you could
 inject a use.

To do this, I added `inject_use_span` field to `ModSpans`, and populate it in
parser (it is the span of the first token found after inner attributes, if any).
Then I rewrote the use-suggestion code to utilize it, and threw out some stuff
that is now unnecessary with this in place. (I think the result is easier to
understand.)

Then I added a test of issue 87613.
---
 compiler/rustc_ast/src/ast.rs                 |   5 +-
 compiler/rustc_ast/src/mut_visit.rs           |   6 +-
 compiler/rustc_ast_lowering/src/item.rs       |   2 +-
 .../rustc_builtin_macros/src/test_harness.rs  |   5 +-
 compiler/rustc_parse/src/parser/item.rs       |   5 +-
 compiler/rustc_resolve/src/lib.rs             | 109 +++++++++---------
 .../ast-json/ast-json-noexpand-output.stdout  |   2 +-
 src/test/ui/ast-json/ast-json-output.stdout   |   2 +-
 src/test/ui/proc-macro/amputate-span.rs       |  63 ++++++++++
 src/test/ui/proc-macro/amputate-span.stderr   |  25 ++++
 .../ui/proc-macro/auxiliary/amputate-span.rs  |  14 +++
 src/tools/rustfmt/src/parse/parser.rs         |   2 +-
 src/tools/rustfmt/src/visitor.rs              |   2 +-
 13 files changed, 172 insertions(+), 70 deletions(-)
 create mode 100644 src/test/ui/proc-macro/amputate-span.rs
 create mode 100644 src/test/ui/proc-macro/amputate-span.stderr
 create mode 100644 src/test/ui/proc-macro/auxiliary/amputate-span.rs

diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index 6a74a19c83e..796dac57744 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -2322,16 +2322,17 @@ pub enum ModKind {
     Unloaded,
 }
 
-#[derive(Clone, Encodable, Decodable, Debug)]
+#[derive(Copy, Clone, Encodable, Decodable, Debug)]
 pub struct ModSpans {
     /// `inner_span` covers the body of the module; for a file module, its the whole file.
     /// For an inline module, its the span inside the `{ ... }`, not including the curly braces.
     pub inner_span: Span,
+    pub inject_use_span: Span,
 }
 
 impl Default for ModSpans {
     fn default() -> ModSpans {
-        ModSpans { inner_span: Default::default() }
+        ModSpans { inner_span: Default::default(), inject_use_span: Default::default() }
     }
 }
 
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index 93588697892..b87637d2dde 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -1009,8 +1009,9 @@ pub fn noop_visit_item_kind<T: MutVisitor>(kind: &mut ItemKind, vis: &mut T) {
         ItemKind::Mod(unsafety, mod_kind) => {
             visit_unsafety(unsafety, vis);
             match mod_kind {
-                ModKind::Loaded(items, _inline, ModSpans { inner_span }) => {
+                ModKind::Loaded(items, _inline, ModSpans { inner_span, inject_use_span }) => {
                     vis.visit_span(inner_span);
+                    vis.visit_span(inject_use_span);
                     items.flat_map_in_place(|item| vis.flat_map_item(item));
                 }
                 ModKind::Unloaded => {}
@@ -1112,8 +1113,9 @@ pub fn noop_visit_crate<T: MutVisitor>(krate: &mut Crate, vis: &mut T) {
     vis.visit_id(id);
     visit_attrs(attrs, vis);
     items.flat_map_in_place(|item| vis.flat_map_item(item));
-    let ModSpans { inner_span } = spans;
+    let ModSpans { inner_span, inject_use_span } = spans;
     vis.visit_span(inner_span);
+    vis.visit_span(inject_use_span);
 }
 
 // Mutates one item into possibly many items.
diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs
index aee117e554f..a7401be6db3 100644
--- a/compiler/rustc_ast_lowering/src/item.rs
+++ b/compiler/rustc_ast_lowering/src/item.rs
@@ -263,7 +263,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
                 })
             }
             ItemKind::Mod(_, ref mod_kind) => match mod_kind {
-                ModKind::Loaded(items, _, ModSpans { inner_span }) => {
+                ModKind::Loaded(items, _, ModSpans { inner_span, inject_use_span: _ }) => {
                     hir::ItemKind::Mod(self.lower_mod(items, *inner_span))
                 }
                 ModKind::Unloaded => panic!("`mod` items should have been loaded by now"),
diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs
index 4bbeece2bde..e2553ab40ca 100644
--- a/compiler/rustc_builtin_macros/src/test_harness.rs
+++ b/compiler/rustc_builtin_macros/src/test_harness.rs
@@ -129,9 +129,8 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> {
 
         // We don't want to recurse into anything other than mods, since
         // mods or tests inside of functions will break things
-        if let ast::ItemKind::Mod(_, ModKind::Loaded(.., ast::ModSpans { inner_span: span })) =
-            item.kind
-        {
+        if let ast::ItemKind::Mod(_, ModKind::Loaded(.., ref spans)) = item.kind {
+            let ast::ModSpans { inner_span: span, inject_use_span: _ } = *spans;
             let prev_tests = mem::take(&mut self.tests);
             noop_visit_item_kind(&mut item.kind, self);
             self.add_test_cases(item.id, span, prev_tests);
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index 484a27fa59d..c370195659d 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -55,6 +55,7 @@ impl<'a> Parser<'a> {
         let lo = self.token.span;
         let attrs = self.parse_inner_attributes()?;
 
+        let post_attr_lo = self.token.span;
         let mut items = vec![];
         while let Some(item) = self.parse_item(ForceCollect::No)? {
             items.push(item);
@@ -71,7 +72,9 @@ impl<'a> Parser<'a> {
             }
         }
 
-        Ok((attrs, items, ModSpans { inner_span: lo.to(self.prev_token.span) }))
+        let inject_use_span = post_attr_lo.data().with_hi(post_attr_lo.lo());
+        let mod_spans = ModSpans { inner_span: lo.to(self.prev_token.span), inject_use_span };
+        Ok((attrs, items, mod_spans))
     }
 }
 
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 9eedd9839eb..03b4f0609bf 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -71,7 +71,6 @@ use rustc_span::{Span, DUMMY_SP};
 use smallvec::{smallvec, SmallVec};
 use std::cell::{Cell, RefCell};
 use std::collections::BTreeSet;
-use std::ops::ControlFlow;
 use std::{cmp, fmt, iter, mem, ptr};
 use tracing::debug;
 
@@ -315,74 +314,70 @@ impl<'a> From<&'a ast::PathSegment> for Segment {
     }
 }
 
+#[derive(Debug)]
 struct UsePlacementFinder {
     target_module: NodeId,
-    span: Option<Span>,
-    found_use: bool,
+    first_legal_span: Option<Span>,
+    first_use_span: Option<Span>,
 }
 
 impl UsePlacementFinder {
     fn check(krate: &Crate, target_module: NodeId) -> (Option<Span>, bool) {
-        let mut finder = UsePlacementFinder { target_module, span: None, found_use: false };
-        if let ControlFlow::Continue(..) = finder.check_mod(&krate.items, CRATE_NODE_ID) {
-            visit::walk_crate(&mut finder, krate);
+        let mut finder =
+            UsePlacementFinder { target_module, first_legal_span: None, first_use_span: None };
+        finder.visit_crate(krate);
+        if let Some(use_span) = finder.first_use_span {
+            (Some(use_span), true)
+        } else {
+            (finder.first_legal_span, false)
         }
-        (finder.span, finder.found_use)
-    }
-
-    fn check_mod(&mut self, items: &[P<ast::Item>], node_id: NodeId) -> ControlFlow<()> {
-        if self.span.is_some() {
-            return ControlFlow::Break(());
-        }
-        if node_id != self.target_module {
-            return ControlFlow::Continue(());
-        }
-        // find a use statement
-        for item in items {
-            match item.kind {
-                ItemKind::Use(..) => {
-                    // don't suggest placing a use before the prelude
-                    // import or other generated ones
-                    if !item.span.from_expansion() {
-                        self.span = Some(item.span.shrink_to_lo());
-                        self.found_use = true;
-                        return ControlFlow::Break(());
-                    }
-                }
-                // don't place use before extern crate
-                ItemKind::ExternCrate(_) => {}
-                // but place them before the first other item
-                _ => {
-                    if self.span.map_or(true, |span| item.span < span)
-                        && !item.span.from_expansion()
-                    {
-                        self.span = Some(item.span.shrink_to_lo());
-                        // don't insert between attributes and an item
-                        // find the first attribute on the item
-                        // FIXME: This is broken for active attributes.
-                        for attr in &item.attrs {
-                            if !attr.span.is_dummy()
-                                && self.span.map_or(true, |span| attr.span < span)
-                            {
-                                self.span = Some(attr.span.shrink_to_lo());
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        ControlFlow::Continue(())
     }
 }
 
-impl<'tcx> Visitor<'tcx> for UsePlacementFinder {
-    fn visit_item(&mut self, item: &'tcx ast::Item) {
-        if let ItemKind::Mod(_, ModKind::Loaded(items, ..)) = &item.kind {
-            if let ControlFlow::Break(..) = self.check_mod(items, item.id) {
-                return;
+fn is_span_suitable_for_use_injection(s: Span) -> bool {
+    // don't suggest placing a use before the prelude
+    // import or other generated ones
+    !s.from_expansion()
+}
+
+fn search_for_any_use_in_items(items: &[P<ast::Item>]) -> Option<Span> {
+    for item in items {
+        if let ItemKind::Use(..) = item.kind {
+            if is_span_suitable_for_use_injection(item.span) {
+                return Some(item.span.shrink_to_lo());
             }
         }
-        visit::walk_item(self, item);
+    }
+    return None;
+}
+
+impl<'tcx> Visitor<'tcx> for UsePlacementFinder {
+    fn visit_crate(&mut self, c: &Crate) {
+        if self.target_module == CRATE_NODE_ID {
+            let inject = c.spans.inject_use_span;
+            if is_span_suitable_for_use_injection(inject) {
+                self.first_legal_span = Some(inject);
+            }
+            self.first_use_span = search_for_any_use_in_items(&c.items);
+            return;
+        } else {
+            visit::walk_crate(self, c);
+        }
+    }
+
+    fn visit_item(&mut self, item: &'tcx ast::Item) {
+        if self.target_module == item.id {
+            if let ItemKind::Mod(_, ModKind::Loaded(items, _inline, mod_spans)) = &item.kind {
+                let inject = mod_spans.inject_use_span;
+                if is_span_suitable_for_use_injection(inject) {
+                    self.first_legal_span = Some(inject);
+                }
+                self.first_use_span = search_for_any_use_in_items(items);
+                return;
+            }
+        } else {
+            visit::walk_item(self, item);
+        }
     }
 }
 
diff --git a/src/test/ui/ast-json/ast-json-noexpand-output.stdout b/src/test/ui/ast-json/ast-json-noexpand-output.stdout
index a456c10cf47..746ced689d4 100644
--- a/src/test/ui/ast-json/ast-json-noexpand-output.stdout
+++ b/src/test/ui/ast-json/ast-json-noexpand-output.stdout
@@ -1 +1 @@
-{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"crate_type","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":{"variant":"Eq","fields":[{"lo":0,"hi":0},{"kind":{"variant":"Interpolated","fields":[{"variant":"NtExpr","fields":[{"id":0,"kind":{"variant":"Lit","fields":[{"token":{"kind":"Str","symbol":"lib","suffix":null},"kind":{"variant":"Str","fields":["lib","Cooked"]},"span":{"lo":0,"hi":0}}]},"span":{"lo":0,"hi":0},"attrs":{"0":null},"tokens":{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}}]}]},"span":{"lo":0,"hi":0}}]},"tokens":null},{"0":[[{"variant":"Token","fields":[{"kind":"Pound","span":{"lo":0,"hi":0}}]},"Joint"],[{"variant":"Token","fields":[{"kind":"Not","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Delimited","fields":[{"open":{"lo":0,"hi":0},"close":{"lo":0,"hi":0}},"Bracket",{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Ident","fields":["crate_type",false]},"span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":"Eq","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}]},"Alone"]]}]},"id":null,"style":"Inner","span":{"lo":0,"hi":0}}],"items":[{"attrs":[],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"core","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null}],"spans":{"inner_span":{"lo":0,"hi":0}},"id":0,"is_placeholder":false}
+{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"crate_type","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":{"variant":"Eq","fields":[{"lo":0,"hi":0},{"kind":{"variant":"Interpolated","fields":[{"variant":"NtExpr","fields":[{"id":0,"kind":{"variant":"Lit","fields":[{"token":{"kind":"Str","symbol":"lib","suffix":null},"kind":{"variant":"Str","fields":["lib","Cooked"]},"span":{"lo":0,"hi":0}}]},"span":{"lo":0,"hi":0},"attrs":{"0":null},"tokens":{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}}]}]},"span":{"lo":0,"hi":0}}]},"tokens":null},{"0":[[{"variant":"Token","fields":[{"kind":"Pound","span":{"lo":0,"hi":0}}]},"Joint"],[{"variant":"Token","fields":[{"kind":"Not","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Delimited","fields":[{"open":{"lo":0,"hi":0},"close":{"lo":0,"hi":0}},"Bracket",{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Ident","fields":["crate_type",false]},"span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":"Eq","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}]},"Alone"]]}]},"id":null,"style":"Inner","span":{"lo":0,"hi":0}}],"items":[{"attrs":[],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"core","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null}],"spans":{"inner_span":{"lo":0,"hi":0},"inject_use_span":{"lo":0,"hi":0}},"id":0,"is_placeholder":false}
diff --git a/src/test/ui/ast-json/ast-json-output.stdout b/src/test/ui/ast-json/ast-json-output.stdout
index d385c76039a..b0aaa663f38 100644
--- a/src/test/ui/ast-json/ast-json-output.stdout
+++ b/src/test/ui/ast-json/ast-json-output.stdout
@@ -1 +1 @@
-{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"crate_type","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":{"variant":"Eq","fields":[{"lo":0,"hi":0},{"kind":{"variant":"Interpolated","fields":[{"variant":"NtExpr","fields":[{"id":0,"kind":{"variant":"Lit","fields":[{"token":{"kind":"Str","symbol":"lib","suffix":null},"kind":{"variant":"Str","fields":["lib","Cooked"]},"span":{"lo":0,"hi":0}}]},"span":{"lo":0,"hi":0},"attrs":{"0":null},"tokens":{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}}]}]},"span":{"lo":0,"hi":0}}]},"tokens":null},{"0":[[{"variant":"Token","fields":[{"kind":"Pound","span":{"lo":0,"hi":0}}]},"Joint"],[{"variant":"Token","fields":[{"kind":"Not","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Delimited","fields":[{"open":{"lo":0,"hi":0},"close":{"lo":0,"hi":0}},"Bracket",{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Ident","fields":["crate_type",false]},"span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":"Eq","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}]},"Alone"]]}]},"id":null,"style":"Inner","span":{"lo":0,"hi":0}}],"items":[{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"prelude_import","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":"Empty","tokens":null},null]},"id":null,"style":"Outer","span":{"lo":0,"hi":0}}],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"","span":{"lo":0,"hi":0}},"kind":{"variant":"Use","fields":[{"prefix":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"{{root}}","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"std","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"prelude","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"rust_2015","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"kind":"Glob","span":{"lo":0,"hi":0}}]},"tokens":null},{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"macro_use","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":"Empty","tokens":null},null]},"id":null,"style":"Outer","span":{"lo":0,"hi":0}}],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"std","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null},{"attrs":[],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"core","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null}],"spans":{"inner_span":{"lo":0,"hi":0}},"id":0,"is_placeholder":false}
+{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"crate_type","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":{"variant":"Eq","fields":[{"lo":0,"hi":0},{"kind":{"variant":"Interpolated","fields":[{"variant":"NtExpr","fields":[{"id":0,"kind":{"variant":"Lit","fields":[{"token":{"kind":"Str","symbol":"lib","suffix":null},"kind":{"variant":"Str","fields":["lib","Cooked"]},"span":{"lo":0,"hi":0}}]},"span":{"lo":0,"hi":0},"attrs":{"0":null},"tokens":{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}}]}]},"span":{"lo":0,"hi":0}}]},"tokens":null},{"0":[[{"variant":"Token","fields":[{"kind":"Pound","span":{"lo":0,"hi":0}}]},"Joint"],[{"variant":"Token","fields":[{"kind":"Not","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Delimited","fields":[{"open":{"lo":0,"hi":0},"close":{"lo":0,"hi":0}},"Bracket",{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Ident","fields":["crate_type",false]},"span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":"Eq","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}]},"Alone"]]}]},"id":null,"style":"Inner","span":{"lo":0,"hi":0}}],"items":[{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"prelude_import","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":"Empty","tokens":null},null]},"id":null,"style":"Outer","span":{"lo":0,"hi":0}}],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"","span":{"lo":0,"hi":0}},"kind":{"variant":"Use","fields":[{"prefix":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"{{root}}","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"std","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"prelude","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"rust_2015","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"kind":"Glob","span":{"lo":0,"hi":0}}]},"tokens":null},{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"macro_use","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":"Empty","tokens":null},null]},"id":null,"style":"Outer","span":{"lo":0,"hi":0}}],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"std","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null},{"attrs":[],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"core","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null}],"spans":{"inner_span":{"lo":0,"hi":0},"inject_use_span":{"lo":0,"hi":0}},"id":0,"is_placeholder":false}
diff --git a/src/test/ui/proc-macro/amputate-span.rs b/src/test/ui/proc-macro/amputate-span.rs
new file mode 100644
index 00000000000..3ca7c847a88
--- /dev/null
+++ b/src/test/ui/proc-macro/amputate-span.rs
@@ -0,0 +1,63 @@
+// aux-build:amputate-span.rs
+// edition:2018
+// compile-flags: --extern amputate_span
+
+// This test has been crafted to ensure the following things:
+//
+// 1. There's a resolution error that prompts the compiler to suggest
+//    adding a `use` item.
+//
+// 2. There are no `use` or `extern crate` items in the source
+//    code. In fact, there is only one item, the `fn main`
+//    declaration.
+//
+// 3. The single `fn main` declaration has an attribute attached to it
+//    that just deletes the first token from the given item.
+//
+// You need all of these conditions to hold in order to replicate the
+// scenario that yielded issue 87613, where the compiler's suggestion
+// looks like:
+//
+// ```
+// help: consider importing this struct
+//    |
+// 47 | hey */ async use std::process::Command;
+//    |              ++++++++++++++++++++++++++
+// ```
+//
+// The first condition is necessary to force the compiler issue a
+// suggestion. The second condition is necessary to force the
+// suggestion to be issued at a span associated with the sole
+// `fn`-item of this crate. The third condition is necessary in order
+// to yield the weird state where the associated span of the `fn`-item
+// does not actually cover all of the original source code of the
+// `fn`-item (which is why we are calling it an "amputated" span
+// here).
+//
+// Note that satisfying conditions 2 and 3 requires the use of the
+// `--extern` compile flag.
+//
+// You might ask yourself: What code would do such a thing?  The
+// answer is: the #[tokio::main] attribute does *exactly* this (as
+// well as injecting some other code into the `fn main` that it
+// constructs).
+
+#[amputate_span::drop_first_token]
+/* what the
+hey */ async fn main() {
+    Command::new("git"); //~ ERROR [E0433]
+}
+
+// (The /* ... */ comment in the above is not part of the original
+// bug. It is just meant to illustrate one particular facet of the
+// original non-ideal behavior, where we were transcribing the
+// trailing comment as part of the emitted suggestion, for better or
+// for worse.)
+
+mod inner {
+    #[amputate_span::drop_first_token]
+        /* another interesting
+    case */ async fn foo() {
+        Command::new("git"); //~ ERROR [E0433]
+    }
+}
diff --git a/src/test/ui/proc-macro/amputate-span.stderr b/src/test/ui/proc-macro/amputate-span.stderr
new file mode 100644
index 00000000000..75c5cbdabc7
--- /dev/null
+++ b/src/test/ui/proc-macro/amputate-span.stderr
@@ -0,0 +1,25 @@
+error[E0433]: failed to resolve: use of undeclared type `Command`
+  --> $DIR/amputate-span.rs:48:5
+   |
+LL |     Command::new("git");
+   |     ^^^^^^^ not found in this scope
+   |
+help: consider importing this struct
+   |
+LL | use std::process::Command;
+   |
+
+error[E0433]: failed to resolve: use of undeclared type `Command`
+  --> $DIR/amputate-span.rs:61:9
+   |
+LL |         Command::new("git");
+   |         ^^^^^^^ not found in this scope
+   |
+help: consider importing this struct
+   |
+LL |     use std::process::Command;
+   |
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0433`.
diff --git a/src/test/ui/proc-macro/auxiliary/amputate-span.rs b/src/test/ui/proc-macro/auxiliary/amputate-span.rs
new file mode 100644
index 00000000000..1a82119ae95
--- /dev/null
+++ b/src/test/ui/proc-macro/auxiliary/amputate-span.rs
@@ -0,0 +1,14 @@
+// force-host
+// no-prefer-dynamic
+
+#![crate_type = "proc-macro"]
+
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+
+#[proc_macro_attribute]
+pub fn drop_first_token(attr: TokenStream, input: TokenStream) -> TokenStream {
+    assert!(attr.is_empty());
+    input.into_iter().skip(1).collect()
+}
diff --git a/src/tools/rustfmt/src/parse/parser.rs b/src/tools/rustfmt/src/parse/parser.rs
index 3b4e762b6dd..6983249c15d 100644
--- a/src/tools/rustfmt/src/parse/parser.rs
+++ b/src/tools/rustfmt/src/parse/parser.rs
@@ -113,7 +113,7 @@ impl<'a> Parser<'a> {
         let result = catch_unwind(AssertUnwindSafe(|| {
             let mut parser = new_parser_from_file(sess.inner(), path, Some(span));
             match parser.parse_mod(&TokenKind::Eof) {
-                Ok((a, i, ast::ModSpans { inner_span })) => Some((a, i, inner_span)),
+                Ok((a, i, ast::ModSpans { inner_span, inject_use_span: _ })) => Some((a, i, inner_span)),
                 Err(mut e) => {
                     e.emit();
                     if sess.can_reset_errors() {
diff --git a/src/tools/rustfmt/src/visitor.rs b/src/tools/rustfmt/src/visitor.rs
index c44b2fc6ae3..dec977e98ca 100644
--- a/src/tools/rustfmt/src/visitor.rs
+++ b/src/tools/rustfmt/src/visitor.rs
@@ -916,7 +916,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
         self.push_str(&ident_str);
 
         if let ast::ModKind::Loaded(ref items, ast::Inline::Yes, ref spans) = mod_kind {
-            let ast::ModSpans { inner_span } = *spans;
+            let ast::ModSpans{ inner_span, inject_use_span: _ } = *spans;
             match self.config.brace_style() {
                 BraceStyle::AlwaysNextLine => {
                     let indent_str = self.block_indent.to_string_with_newline(self.config);