diff --git a/config.toml.example b/config.toml.example
index f45db37c33b86..b4ad15a823578 100644
--- a/config.toml.example
+++ b/config.toml.example
@@ -162,6 +162,9 @@
 # Python interpreter to use for various tasks throughout the build, notably
 # rustdoc tests, the lldb python interpreter, and some dist bits and pieces.
 # Note that Python 2 is currently required.
+#
+# Defaults to python2.7, then python2. If neither executable can be found, then
+# it defaults to the Python interpreter used to execute x.py.
 #python = "python2.7"
 
 # Force Cargo to check that Cargo.lock describes the precise dependency
diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py
index 119b38bcc99dc..c98d8b8ecf437 100644
--- a/src/bootstrap/bootstrap.py
+++ b/src/bootstrap/bootstrap.py
@@ -830,7 +830,7 @@ def main():
 
     # x.py help <cmd> ...
     if len(sys.argv) > 1 and sys.argv[1] == 'help':
-        sys.argv = sys.argv[:1] + [sys.argv[2], '-h'] + sys.argv[3:]
+        sys.argv = [sys.argv[0], '-h'] + sys.argv[2:]
 
     help_triggered = (
         '-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
diff --git a/src/bootstrap/sanity.rs b/src/bootstrap/sanity.rs
index ff4fb85bbfad3..4b9d0cc15d665 100644
--- a/src/bootstrap/sanity.rs
+++ b/src/bootstrap/sanity.rs
@@ -107,9 +107,9 @@ pub fn check(build: &mut Build) {
     }
 
     build.config.python = build.config.python.take().map(|p| cmd_finder.must_have(p))
-        .or_else(|| env::var_os("BOOTSTRAP_PYTHON").map(PathBuf::from)) // set by bootstrap.py
         .or_else(|| cmd_finder.maybe_have("python2.7"))
         .or_else(|| cmd_finder.maybe_have("python2"))
+        .or_else(|| env::var_os("BOOTSTRAP_PYTHON").map(PathBuf::from)) // set by bootstrap.py
         .or_else(|| Some(cmd_finder.must_have("python")));
 
     build.config.nodejs = build.config.nodejs.take().map(|p| cmd_finder.must_have(p))
diff --git a/src/etc/lldb_rust_formatters.py b/src/etc/lldb_rust_formatters.py
index 2c651c90f82eb..fdc1c4fa0cc38 100644
--- a/src/etc/lldb_rust_formatters.py
+++ b/src/etc/lldb_rust_formatters.py
@@ -290,6 +290,8 @@ def render_element(i):
 
 
 def read_utf8_string(ptr_val, byte_count):
+    if byte_count == 0:
+        return '""'
     error = lldb.SBError()
     process = ptr_val.get_wrapped_value().GetProcess()
     data = process.ReadMemory(ptr_val.as_integer(), byte_count, error)
diff --git a/src/libcore/num/mod.rs b/src/libcore/num/mod.rs
index 5b7d5f45d9246..7a5badb487ecf 100644
--- a/src/libcore/num/mod.rs
+++ b/src/libcore/num/mod.rs
@@ -4613,7 +4613,7 @@ macro_rules! rev {
     )*}
 }
 
-/// intra-sign conversions
+// intra-sign conversions
 try_from_upper_bounded!(u16, u8);
 try_from_upper_bounded!(u32, u16, u8);
 try_from_upper_bounded!(u64, u32, u16, u8);
diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs
index df02b91996f34..36c3e8d449e7d 100644
--- a/src/librustc/hir/mod.rs
+++ b/src/librustc/hir/mod.rs
@@ -115,15 +115,15 @@ impl serialize::UseSpecializedDecodable for HirId {
 // hack to ensure that we don't try to access the private parts of `ItemLocalId` in this module
 mod item_local_id_inner {
     use rustc_data_structures::indexed_vec::Idx;
-    /// An `ItemLocalId` uniquely identifies something within a given "item-like",
-    /// that is within a hir::Item, hir::TraitItem, or hir::ImplItem. There is no
-    /// guarantee that the numerical value of a given `ItemLocalId` corresponds to
-    /// the node's position within the owning item in any way, but there is a
-    /// guarantee that the `LocalItemId`s within an owner occupy a dense range of
-    /// integers starting at zero, so a mapping that maps all or most nodes within
-    /// an "item-like" to something else can be implement by a `Vec` instead of a
-    /// tree or hash map.
     newtype_index! {
+        /// An `ItemLocalId` uniquely identifies something within a given "item-like",
+        /// that is within a hir::Item, hir::TraitItem, or hir::ImplItem. There is no
+        /// guarantee that the numerical value of a given `ItemLocalId` corresponds to
+        /// the node's position within the owning item in any way, but there is a
+        /// guarantee that the `LocalItemId`s within an owner occupy a dense range of
+        /// integers starting at zero, so a mapping that maps all or most nodes within
+        /// an "item-like" to something else can be implement by a `Vec` instead of a
+        /// tree or hash map.
         pub struct ItemLocalId { .. }
     }
 }
diff --git a/src/librustc/middle/region.rs b/src/librustc/middle/region.rs
index fd188b33d7e1f..6527ed46cbc9e 100644
--- a/src/librustc/middle/region.rs
+++ b/src/librustc/middle/region.rs
@@ -132,25 +132,24 @@ pub enum ScopeData {
     Remainder(FirstStatementIndex)
 }
 
-/// Represents a subscope of `block` for a binding that is introduced
-/// by `block.stmts[first_statement_index]`. Such subscopes represent
-/// a suffix of the block. Note that each subscope does not include
-/// the initializer expression, if any, for the statement indexed by
-/// `first_statement_index`.
-///
-/// For example, given `{ let (a, b) = EXPR_1; let c = EXPR_2; ... }`:
-///
-/// * The subscope with `first_statement_index == 0` is scope of both
-///   `a` and `b`; it does not include EXPR_1, but does include
-///   everything after that first `let`. (If you want a scope that
-///   includes EXPR_1 as well, then do not use `Scope::Remainder`,
-///   but instead another `Scope` that encompasses the whole block,
-///   e.g., `Scope::Node`.
-///
-/// * The subscope with `first_statement_index == 1` is scope of `c`,
-///   and thus does not include EXPR_2, but covers the `...`.
-
 newtype_index! {
+    /// Represents a subscope of `block` for a binding that is introduced
+    /// by `block.stmts[first_statement_index]`. Such subscopes represent
+    /// a suffix of the block. Note that each subscope does not include
+    /// the initializer expression, if any, for the statement indexed by
+    /// `first_statement_index`.
+    ///
+    /// For example, given `{ let (a, b) = EXPR_1; let c = EXPR_2; ... }`:
+    ///
+    /// * The subscope with `first_statement_index == 0` is scope of both
+    ///   `a` and `b`; it does not include EXPR_1, but does include
+    ///   everything after that first `let`. (If you want a scope that
+    ///   includes EXPR_1 as well, then do not use `Scope::Remainder`,
+    ///   but instead another `Scope` that encompasses the whole block,
+    ///   e.g., `Scope::Node`.
+    ///
+    /// * The subscope with `first_statement_index == 1` is scope of `c`,
+    ///   and thus does not include EXPR_2, but covers the `...`.
     pub struct FirstStatementIndex { .. }
 }
 
diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs
index 6bb3222512565..5313098bcc535 100644
--- a/src/librustc/ty/context.rs
+++ b/src/librustc/ty/context.rs
@@ -1883,9 +1883,11 @@ pub mod tls {
         rayon_core::tlv::get()
     }
 
-    /// A thread local variable which stores a pointer to the current ImplicitCtxt
     #[cfg(not(parallel_compiler))]
-    thread_local!(static TLV: Cell<usize> = Cell::new(0));
+    thread_local!(
+        /// A thread local variable which stores a pointer to the current ImplicitCtxt
+        static TLV: Cell<usize> = Cell::new(0)
+    );
 
     /// Sets TLV to `value` during the call to `f`.
     /// It is restored to its previous value after.
@@ -2002,10 +2004,15 @@ pub mod tls {
         })
     }
 
-    /// Stores a pointer to the GlobalCtxt if one is available.
-    /// This is used to access the GlobalCtxt in the deadlock handler
-    /// given to Rayon.
-    scoped_thread_local!(pub static GCX_PTR: Lock<usize>);
+    scoped_thread_local! {
+        // FIXME: This should be a doc comment, but the macro does not allow attributes:
+        // https://github.com/alexcrichton/scoped-tls/pull/8
+        //
+        // Stores a pointer to the GlobalCtxt if one is available.
+        // This is used to access the GlobalCtxt in the deadlock handler
+        // given to Rayon.
+        pub static GCX_PTR: Lock<usize>
+    }
 
     /// Creates a TyCtxt and ImplicitCtxt based on the GCX_PTR thread local.
     /// This is used in the deadlock handler.
diff --git a/src/librustc/ty/mod.rs b/src/librustc/ty/mod.rs
index 02e4fb12af5c9..eed9c3ed7ef95 100644
--- a/src/librustc/ty/mod.rs
+++ b/src/librustc/ty/mod.rs
@@ -1489,42 +1489,42 @@ impl<'tcx> InstantiatedPredicates<'tcx> {
     }
 }
 
-/// "Universes" are used during type- and trait-checking in the
-/// presence of `for<..>` binders to control what sets of names are
-/// visible. Universes are arranged into a tree: the root universe
-/// contains names that are always visible. Each child then adds a new
-/// set of names that are visible, in addition to those of its parent.
-/// We say that the child universe "extends" the parent universe with
-/// new names.
-///
-/// To make this more concrete, consider this program:
-///
-/// ```
-/// struct Foo { }
-/// fn bar<T>(x: T) {
-///   let y: for<'a> fn(&'a u8, Foo) = ...;
-/// }
-/// ```
-///
-/// The struct name `Foo` is in the root universe U0. But the type
-/// parameter `T`, introduced on `bar`, is in an extended universe U1
-/// -- i.e., within `bar`, we can name both `T` and `Foo`, but outside
-/// of `bar`, we cannot name `T`. Then, within the type of `y`, the
-/// region `'a` is in a universe U2 that extends U1, because we can
-/// name it inside the fn type but not outside.
-///
-/// Universes are used to do type- and trait-checking around these
-/// "forall" binders (also called **universal quantification**). The
-/// idea is that when, in the body of `bar`, we refer to `T` as a
-/// type, we aren't referring to any type in particular, but rather a
-/// kind of "fresh" type that is distinct from all other types we have
-/// actually declared. This is called a **placeholder** type, and we
-/// use universes to talk about this. In other words, a type name in
-/// universe 0 always corresponds to some "ground" type that the user
-/// declared, but a type name in a non-zero universe is a placeholder
-/// type -- an idealized representative of "types in general" that we
-/// use for checking generic functions.
 newtype_index! {
+    /// "Universes" are used during type- and trait-checking in the
+    /// presence of `for<..>` binders to control what sets of names are
+    /// visible. Universes are arranged into a tree: the root universe
+    /// contains names that are always visible. Each child then adds a new
+    /// set of names that are visible, in addition to those of its parent.
+    /// We say that the child universe "extends" the parent universe with
+    /// new names.
+    ///
+    /// To make this more concrete, consider this program:
+    ///
+    /// ```
+    /// struct Foo { }
+    /// fn bar<T>(x: T) {
+    ///   let y: for<'a> fn(&'a u8, Foo) = ...;
+    /// }
+    /// ```
+    ///
+    /// The struct name `Foo` is in the root universe U0. But the type
+    /// parameter `T`, introduced on `bar`, is in an extended universe U1
+    /// -- i.e., within `bar`, we can name both `T` and `Foo`, but outside
+    /// of `bar`, we cannot name `T`. Then, within the type of `y`, the
+    /// region `'a` is in a universe U2 that extends U1, because we can
+    /// name it inside the fn type but not outside.
+    ///
+    /// Universes are used to do type- and trait-checking around these
+    /// "forall" binders (also called **universal quantification**). The
+    /// idea is that when, in the body of `bar`, we refer to `T` as a
+    /// type, we aren't referring to any type in particular, but rather a
+    /// kind of "fresh" type that is distinct from all other types we have
+    /// actually declared. This is called a **placeholder** type, and we
+    /// use universes to talk about this. In other words, a type name in
+    /// universe 0 always corresponds to some "ground" type that the user
+    /// declared, but a type name in a non-zero universe is a placeholder
+    /// type -- an idealized representative of "types in general" that we
+    /// use for checking generic functions.
     pub struct UniverseIndex {
         DEBUG_FORMAT = "U{}",
     }
diff --git a/src/librustc/ty/sty.rs b/src/librustc/ty/sty.rs
index dd382ec006bd7..deed7084f3a97 100644
--- a/src/librustc/ty/sty.rs
+++ b/src/librustc/ty/sty.rs
@@ -1061,46 +1061,46 @@ impl<'a, 'gcx, 'tcx> ParamTy {
     }
 }
 
-/// A [De Bruijn index][dbi] is a standard means of representing
-/// regions (and perhaps later types) in a higher-ranked setting. In
-/// particular, imagine a type like this:
-///
-///     for<'a> fn(for<'b> fn(&'b isize, &'a isize), &'a char)
-///     ^          ^            |        |         |
-///     |          |            |        |         |
-///     |          +------------+ 0      |         |
-///     |                                |         |
-///     +--------------------------------+ 1       |
-///     |                                          |
-///     +------------------------------------------+ 0
-///
-/// In this type, there are two binders (the outer fn and the inner
-/// fn). We need to be able to determine, for any given region, which
-/// fn type it is bound by, the inner or the outer one. There are
-/// various ways you can do this, but a De Bruijn index is one of the
-/// more convenient and has some nice properties. The basic idea is to
-/// count the number of binders, inside out. Some examples should help
-/// clarify what I mean.
-///
-/// Let's start with the reference type `&'b isize` that is the first
-/// argument to the inner function. This region `'b` is assigned a De
-/// Bruijn index of 0, meaning "the innermost binder" (in this case, a
-/// fn). The region `'a` that appears in the second argument type (`&'a
-/// isize`) would then be assigned a De Bruijn index of 1, meaning "the
-/// second-innermost binder". (These indices are written on the arrays
-/// in the diagram).
-///
-/// What is interesting is that De Bruijn index attached to a particular
-/// variable will vary depending on where it appears. For example,
-/// the final type `&'a char` also refers to the region `'a` declared on
-/// the outermost fn. But this time, this reference is not nested within
-/// any other binders (i.e., it is not an argument to the inner fn, but
-/// rather the outer one). Therefore, in this case, it is assigned a
-/// De Bruijn index of 0, because the innermost binder in that location
-/// is the outer fn.
-///
-/// [dbi]: http://en.wikipedia.org/wiki/De_Bruijn_index
 newtype_index! {
+    /// A [De Bruijn index][dbi] is a standard means of representing
+    /// regions (and perhaps later types) in a higher-ranked setting. In
+    /// particular, imagine a type like this:
+    ///
+    ///     for<'a> fn(for<'b> fn(&'b isize, &'a isize), &'a char)
+    ///     ^          ^            |        |         |
+    ///     |          |            |        |         |
+    ///     |          +------------+ 0      |         |
+    ///     |                                |         |
+    ///     +--------------------------------+ 1       |
+    ///     |                                          |
+    ///     +------------------------------------------+ 0
+    ///
+    /// In this type, there are two binders (the outer fn and the inner
+    /// fn). We need to be able to determine, for any given region, which
+    /// fn type it is bound by, the inner or the outer one. There are
+    /// various ways you can do this, but a De Bruijn index is one of the
+    /// more convenient and has some nice properties. The basic idea is to
+    /// count the number of binders, inside out. Some examples should help
+    /// clarify what I mean.
+    ///
+    /// Let's start with the reference type `&'b isize` that is the first
+    /// argument to the inner function. This region `'b` is assigned a De
+    /// Bruijn index of 0, meaning "the innermost binder" (in this case, a
+    /// fn). The region `'a` that appears in the second argument type (`&'a
+    /// isize`) would then be assigned a De Bruijn index of 1, meaning "the
+    /// second-innermost binder". (These indices are written on the arrays
+    /// in the diagram).
+    ///
+    /// What is interesting is that De Bruijn index attached to a particular
+    /// variable will vary depending on where it appears. For example,
+    /// the final type `&'a char` also refers to the region `'a` declared on
+    /// the outermost fn. But this time, this reference is not nested within
+    /// any other binders (i.e., it is not an argument to the inner fn, but
+    /// rather the outer one). Therefore, in this case, it is assigned a
+    /// De Bruijn index of 0, because the innermost binder in that location
+    /// is the outer fn.
+    ///
+    /// [dbi]: http://en.wikipedia.org/wiki/De_Bruijn_index
     pub struct DebruijnIndex {
         DEBUG_FORMAT = "DebruijnIndex({})",
         const INNERMOST = 0,
diff --git a/src/librustc_data_structures/indexed_vec.rs b/src/librustc_data_structures/indexed_vec.rs
index 09aec50e4bb11..1153c3e79bfd5 100644
--- a/src/librustc_data_structures/indexed_vec.rs
+++ b/src/librustc_data_structures/indexed_vec.rs
@@ -58,9 +58,10 @@ macro_rules! newtype_index {
     // ---- public rules ----
 
     // Use default constants
-    ($v:vis struct $name:ident { .. }) => (
+    ($(#[$attrs:meta])* $v:vis struct $name:ident { .. }) => (
         newtype_index!(
             // Leave out derives marker so we can use its absence to ensure it comes first
+            @attrs        [$(#[$attrs])*]
             @type         [$name]
             // shave off 256 indices at the end to allow space for packing these indices into enums
             @max          [0xFFFF_FF00]
@@ -69,9 +70,10 @@ macro_rules! newtype_index {
     );
 
     // Define any constants
-    ($v:vis struct $name:ident { $($tokens:tt)+ }) => (
+    ($(#[$attrs:meta])* $v:vis struct $name:ident { $($tokens:tt)+ }) => (
         newtype_index!(
             // Leave out derives marker so we can use its absence to ensure it comes first
+            @attrs        [$(#[$attrs])*]
             @type         [$name]
             // shave off 256 indices at the end to allow space for packing these indices into enums
             @max          [0xFFFF_FF00]
@@ -84,10 +86,12 @@ macro_rules! newtype_index {
 
     // Base case, user-defined constants (if any) have already been defined
     (@derives      [$($derives:ident,)*]
+     @attrs        [$(#[$attrs:meta])*]
      @type         [$type:ident]
      @max          [$max:expr]
      @vis          [$v:vis]
      @debug_format [$debug_format:tt]) => (
+        $(#[$attrs])*
         #[derive(Copy, PartialEq, Eq, Hash, PartialOrd, Ord, $($derives),*)]
         #[rustc_layout_scalar_valid_range_end($max)]
         $v struct $type {
@@ -317,7 +321,8 @@ macro_rules! newtype_index {
 
     // By not including the @derives marker in this list nor in the default args, we can force it
     // to come first if it exists. When encodable isn't custom, add serialization traits by default.
-    (@type         [$type:ident]
+    (@attrs        [$(#[$attrs:meta])*]
+     @type         [$type:ident]
      @max          [$max:expr]
      @vis          [$v:vis]
      @debug_format [$debug_format:tt]
@@ -325,6 +330,7 @@ macro_rules! newtype_index {
                    $($tokens:tt)*) => (
         newtype_index!(
             @derives      [$($derives,)+ RustcEncodable,]
+            @attrs        [$(#[$attrs])*]
             @type         [$type]
             @max          [$max]
             @vis          [$v]
@@ -335,7 +341,8 @@ macro_rules! newtype_index {
 
     // The case where no derives are added, but encodable is overridden. Don't
     // derive serialization traits
-    (@type         [$type:ident]
+    (@attrs        [$(#[$attrs:meta])*]
+     @type         [$type:ident]
      @max          [$max:expr]
      @vis          [$v:vis]
      @debug_format [$debug_format:tt]
@@ -343,6 +350,7 @@ macro_rules! newtype_index {
                    $($tokens:tt)*) => (
         newtype_index!(
             @derives      []
+            @attrs        [$(#[$attrs])*]
             @type         [$type]
             @max          [$max]
             @vis          [$v]
@@ -351,13 +359,15 @@ macro_rules! newtype_index {
     );
 
     // The case where no derives are added, add serialization derives by default
-    (@type         [$type:ident]
+    (@attrs        [$(#[$attrs:meta])*]
+     @type         [$type:ident]
      @max          [$max:expr]
      @vis          [$v:vis]
      @debug_format [$debug_format:tt]
                    $($tokens:tt)*) => (
         newtype_index!(
             @derives      [RustcEncodable,]
+            @attrs        [$(#[$attrs])*]
             @type         [$type]
             @max          [$max]
             @vis          [$v]
@@ -384,6 +394,7 @@ macro_rules! newtype_index {
 
     // Rewrite final without comma to one that includes comma
     (@derives      [$($derives:ident,)*]
+     @attrs        [$(#[$attrs:meta])*]
      @type         [$type:ident]
      @max          [$max:expr]
      @vis          [$v:vis]
@@ -391,6 +402,7 @@ macro_rules! newtype_index {
                    $name:ident = $constant:expr) => (
         newtype_index!(
             @derives      [$($derives,)*]
+            @attrs        [$(#[$attrs])*]
             @type         [$type]
             @max          [$max]
             @vis          [$v]
@@ -400,6 +412,7 @@ macro_rules! newtype_index {
 
     // Rewrite final const without comma to one that includes comma
     (@derives      [$($derives:ident,)*]
+     @attrs        [$(#[$attrs:meta])*]
      @type         [$type:ident]
      @max          [$_max:expr]
      @vis          [$v:vis]
@@ -408,6 +421,7 @@ macro_rules! newtype_index {
                    const $name:ident = $constant:expr) => (
         newtype_index!(
             @derives      [$($derives,)*]
+            @attrs        [$(#[$attrs])*]
             @type         [$type]
             @max          [$max]
             @vis          [$v]
@@ -417,6 +431,7 @@ macro_rules! newtype_index {
 
     // Replace existing default for max
     (@derives      [$($derives:ident,)*]
+     @attrs        [$(#[$attrs:meta])*]
      @type         [$type:ident]
      @max          [$_max:expr]
      @vis          [$v:vis]
@@ -425,6 +440,7 @@ macro_rules! newtype_index {
                    $($tokens:tt)*) => (
         newtype_index!(
             @derives      [$($derives,)*]
+            @attrs        [$(#[$attrs])*]
             @type         [$type]
             @max          [$max]
             @vis          [$v]
@@ -434,6 +450,7 @@ macro_rules! newtype_index {
 
     // Replace existing default for debug_format
     (@derives      [$($derives:ident,)*]
+     @attrs        [$(#[$attrs:meta])*]
      @type         [$type:ident]
      @max          [$max:expr]
      @vis          [$v:vis]
@@ -442,6 +459,7 @@ macro_rules! newtype_index {
                    $($tokens:tt)*) => (
         newtype_index!(
             @derives      [$($derives,)*]
+            @attrs        [$(#[$attrs])*]
             @type         [$type]
             @max          [$max]
             @vis          [$v]
@@ -451,6 +469,7 @@ macro_rules! newtype_index {
 
     // Assign a user-defined constant
     (@derives      [$($derives:ident,)*]
+     @attrs        [$(#[$attrs:meta])*]
      @type         [$type:ident]
      @max          [$max:expr]
      @vis          [$v:vis]
@@ -462,6 +481,7 @@ macro_rules! newtype_index {
         pub const $name: $type = $type::from_u32_const($constant);
         newtype_index!(
             @derives      [$($derives,)*]
+            @attrs        [$(#[$attrs])*]
             @type         [$type]
             @max          [$max]
             @vis          [$v]
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index 40c9ef3f63cf5..5ad50dca4adb6 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -36,7 +36,7 @@ use syntax::tokenstream::{TokenTree, TokenStream};
 use syntax::ast;
 use syntax::ptr::P;
 use syntax::ast::Expr;
-use syntax::attr;
+use syntax::attr::{self, HasAttrs};
 use syntax::source_map::Spanned;
 use syntax::edition::Edition;
 use syntax::feature_gate::{AttributeGate, AttributeTemplate, AttributeType};
@@ -799,27 +799,81 @@ impl LintPass for UnusedDocComment {
 }
 
 impl UnusedDocComment {
-    fn warn_if_doc<'a, 'tcx,
-                   I: Iterator<Item=&'a ast::Attribute>,
-                   C: LintContext<'tcx>>(&self, mut attrs: I, cx: &C) {
-        if let Some(attr) = attrs.find(|a| a.is_value_str() && a.check_name("doc")) {
-            cx.struct_span_lint(UNUSED_DOC_COMMENTS, attr.span, "doc comment not used by rustdoc")
-              .emit();
+    fn warn_if_doc(
+        &self,
+        cx: &EarlyContext<'_>,
+        node_span: Span,
+        node_kind: &str,
+        is_macro_expansion: bool,
+        attrs: &[ast::Attribute]
+    ) {
+        let mut attrs = attrs.into_iter().peekable();
+
+        // Accumulate a single span for sugared doc comments.
+        let mut sugared_span: Option<Span> = None;
+
+        while let Some(attr) = attrs.next() {
+            if attr.is_sugared_doc {
+                sugared_span = Some(
+                    sugared_span.map_or_else(
+                        || attr.span,
+                        |span| span.with_hi(attr.span.hi()),
+                    ),
+                );
+            }
+
+            if attrs.peek().map(|next_attr| next_attr.is_sugared_doc).unwrap_or_default() {
+                continue;
+            }
+
+            let span = sugared_span.take().unwrap_or_else(|| attr.span);
+
+            if attr.name() == "doc" {
+                let mut err = cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, "unused doc comment");
+
+                err.span_label(
+                    node_span,
+                    format!("rustdoc does not generate documentation for {}", node_kind)
+                );
+
+                if is_macro_expansion {
+                    err.help("to document an item produced by a macro, \
+                              the macro must produce the documentation as part of its expansion");
+                }
+
+                err.emit();
+            }
         }
     }
 }
 
 impl EarlyLintPass for UnusedDocComment {
-    fn check_local(&mut self, cx: &EarlyContext<'_>, decl: &ast::Local) {
-        self.warn_if_doc(decl.attrs.iter(), cx);
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        if let ast::ItemKind::Mac(..) = item.node {
+            self.warn_if_doc(cx, item.span, "macro expansions", true, &item.attrs);
+        }
+    }
+
+    fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
+        let (kind, is_macro_expansion) = match stmt.node {
+            ast::StmtKind::Local(..) => ("statements", false),
+            ast::StmtKind::Item(..) => ("inner items", false),
+            ast::StmtKind::Mac(..) => ("macro expansions", true),
+            // expressions will be reported by `check_expr`.
+            ast::StmtKind::Semi(..) |
+            ast::StmtKind::Expr(..) => return,
+        };
+
+        self.warn_if_doc(cx, stmt.span, kind, is_macro_expansion, stmt.node.attrs());
     }
 
     fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
-        self.warn_if_doc(arm.attrs.iter(), cx);
+        let arm_span = arm.pats[0].span.with_hi(arm.body.span.hi());
+        self.warn_if_doc(cx, arm_span, "match arms", false, &arm.attrs);
     }
 
     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
-        self.warn_if_doc(expr.attrs.iter(), cx);
+        self.warn_if_doc(cx, expr.span, "expressions", false, &expr.attrs);
     }
 }
 
diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs
index 5c243e1389073..5e375dcaa0681 100644
--- a/src/librustc_lint/lib.rs
+++ b/src/librustc_lint/lib.rs
@@ -66,6 +66,7 @@ macro_rules! pre_expansion_lint_passes {
     ($macro:path, $args:tt) => (
         $macro!($args, [
             KeywordIdents: KeywordIdents,
+            UnusedDocComment: UnusedDocComment,
         ]);
     )
 }
@@ -77,7 +78,6 @@ macro_rules! early_lint_passes {
             UnusedImportBraces: UnusedImportBraces,
             UnsafeCode: UnsafeCode,
             AnonymousParameters: AnonymousParameters,
-            UnusedDocComment: UnusedDocComment,
             EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns,
             NonCamelCaseTypes: NonCamelCaseTypes,
             DeprecatedAttr: DeprecatedAttr::new(),
diff --git a/src/librustc_mir/borrow_check/nll/region_infer/values.rs b/src/librustc_mir/borrow_check/nll/region_infer/values.rs
index ef27fdbde387f..49b83315ce6e6 100644
--- a/src/librustc_mir/borrow_check/nll/region_infer/values.rs
+++ b/src/librustc_mir/borrow_check/nll/region_infer/values.rs
@@ -116,14 +116,14 @@ impl RegionValueElements {
     }
 }
 
-/// A single integer representing a `Location` in the MIR control-flow
-/// graph. Constructed efficiently from `RegionValueElements`.
 newtype_index! {
+    /// A single integer representing a `Location` in the MIR control-flow
+    /// graph. Constructed efficiently from `RegionValueElements`.
     pub struct PointIndex { DEBUG_FORMAT = "PointIndex({})" }
 }
 
-/// A single integer representing a `ty::Placeholder`.
 newtype_index! {
+    /// A single integer representing a `ty::Placeholder`.
     pub struct PlaceholderIndex { DEBUG_FORMAT = "PlaceholderIndex({})" }
 }
 
diff --git a/src/librustc_mir/borrow_check/nll/type_check/liveness/liveness_map.rs b/src/librustc_mir/borrow_check/nll/type_check/liveness/liveness_map.rs
index b9f9d83161b79..fe60f8471629f 100644
--- a/src/librustc_mir/borrow_check/nll/type_check/liveness/liveness_map.rs
+++ b/src/librustc_mir/borrow_check/nll/type_check/liveness/liveness_map.rs
@@ -85,10 +85,10 @@ impl NllLivenessMap {
     }
 }
 
-/// Index given to each local variable for which we need to
-/// compute liveness information. For many locals, we are able to
-/// skip liveness information: for example, those variables whose
-/// types contain no regions.
 newtype_index! {
+    /// Index given to each local variable for which we need to
+    /// compute liveness information. For many locals, we are able to
+    /// skip liveness information: for example, those variables whose
+    /// types contain no regions.
     pub struct LiveVar { .. }
 }
diff --git a/src/librustc_mir/dataflow/move_paths/mod.rs b/src/librustc_mir/dataflow/move_paths/mod.rs
index efd979a7da4fb..07b93161e7218 100644
--- a/src/librustc_mir/dataflow/move_paths/mod.rs
+++ b/src/librustc_mir/dataflow/move_paths/mod.rs
@@ -23,7 +23,7 @@ pub(crate) mod indexes {
     use rustc_data_structures::indexed_vec::Idx;
 
     macro_rules! new_index {
-        ($Index:ident, $debug_name:expr) => {
+        ($(#[$attrs:meta])* $Index:ident, $debug_name:expr) => {
             #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
             pub struct $Index(NonZeroUsize);
 
@@ -44,17 +44,29 @@ pub(crate) mod indexes {
         }
     }
 
-    /// Index into MovePathData.move_paths
-    new_index!(MovePathIndex, "mp");
-
-    /// Index into MoveData.moves.
-    new_index!(MoveOutIndex, "mo");
-
-    /// Index into MoveData.inits.
-    new_index!(InitIndex, "in");
-
-    /// Index into Borrows.locations
-    new_index!(BorrowIndex, "bw");
+    new_index!(
+        /// Index into MovePathData.move_paths
+        MovePathIndex,
+        "mp"
+    );
+
+    new_index!(
+        /// Index into MoveData.moves.
+        MoveOutIndex,
+        "mo"
+    );
+
+    new_index!(
+        /// Index into MoveData.inits.
+        InitIndex,
+        "in"
+    );
+
+    new_index!(
+        /// Index into Borrows.locations
+        BorrowIndex,
+        "bw"
+    );
 }
 
 pub use self::indexes::MovePathIndex;
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index e5caf7fdfa235..b1c53ea92b300 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -214,7 +214,7 @@ impl Options {
         if matches.opt_strs("passes") == ["list"] {
             println!("Available passes for running rustdoc:");
             for pass in passes::PASSES {
-                println!("{:>20} - {}", pass.name(), pass.description());
+                println!("{:>20} - {}", pass.name, pass.description);
             }
             println!("\nDefault passes for rustdoc:");
             for &name in passes::DEFAULT_PASSES {
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 4f70751c90537..bf95cde163fc3 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -606,10 +606,12 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
                 passes::defaults(default_passes).iter().map(|p| p.to_string()).collect();
             passes.extend(manual_passes);
 
+            info!("Executing passes");
+
             for pass in &passes {
-                // the "unknown pass" error will be reported when late passes are run
-                if let Some(pass) = passes::find_pass(pass).and_then(|p| p.early_fn()) {
-                    krate = pass(krate, &ctxt);
+                match passes::find_pass(pass).map(|p| p.pass) {
+                    Some(pass) => krate = pass(krate, &ctxt),
+                    None => error!("unknown pass {}, skipping", *pass),
                 }
             }
 
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 5e9f9ee9f80af..39e504951d1c6 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -441,28 +441,6 @@ where R: 'static + Send,
 
         krate.version = crate_version;
 
-        info!("Executing passes");
-
-        for pass in &passes {
-            // determine if we know about this pass
-            let pass = match passes::find_pass(pass) {
-                Some(pass) => if let Some(pass) = pass.late_fn() {
-                    pass
-                } else {
-                    // not a late pass, but still valid so don't report the error
-                    continue
-                }
-                None => {
-                    error!("unknown pass {}, skipping", *pass);
-
-                    continue
-                },
-            };
-
-            // run it
-            krate = pass(krate);
-        }
-
         tx.send(f(Output {
             krate: krate,
             renderinfo: renderinfo,
diff --git a/src/librustdoc/passes/check_code_block_syntax.rs b/src/librustdoc/passes/check_code_block_syntax.rs
index f960374370e04..88d9c87c52898 100644
--- a/src/librustdoc/passes/check_code_block_syntax.rs
+++ b/src/librustdoc/passes/check_code_block_syntax.rs
@@ -10,9 +10,11 @@ use crate::fold::DocFolder;
 use crate::html::markdown::{self, RustCodeBlock};
 use crate::passes::Pass;
 
-pub const CHECK_CODE_BLOCK_SYNTAX: Pass =
-    Pass::early("check-code-block-syntax", check_code_block_syntax,
-                "validates syntax inside Rust code blocks");
+pub const CHECK_CODE_BLOCK_SYNTAX: Pass = Pass {
+    name: "check-code-block-syntax",
+    pass: check_code_block_syntax,
+    description: "validates syntax inside Rust code blocks",
+};
 
 pub fn check_code_block_syntax(krate: clean::Crate, cx: &DocContext<'_, '_, '_>) -> clean::Crate {
     SyntaxChecker { cx }.fold_crate(krate)
diff --git a/src/librustdoc/passes/collapse_docs.rs b/src/librustdoc/passes/collapse_docs.rs
index e5e60cbe71700..088a6ea77c73f 100644
--- a/src/librustdoc/passes/collapse_docs.rs
+++ b/src/librustdoc/passes/collapse_docs.rs
@@ -1,13 +1,16 @@
 use crate::clean::{self, DocFragment, Item};
+use crate::core::DocContext;
 use crate::fold;
 use crate::fold::{DocFolder};
 use crate::passes::Pass;
 
 use std::mem::replace;
 
-pub const COLLAPSE_DOCS: Pass =
-    Pass::late("collapse-docs", collapse_docs,
-        "concatenates all document attributes into one document attribute");
+pub const COLLAPSE_DOCS: Pass = Pass {
+    name: "collapse-docs",
+    pass: collapse_docs,
+    description: "concatenates all document attributes into one document attribute",
+};
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 enum DocFragmentKind {
@@ -26,7 +29,7 @@ impl DocFragment {
     }
 }
 
-pub fn collapse_docs(krate: clean::Crate) -> clean::Crate {
+pub fn collapse_docs(krate: clean::Crate, _: &DocContext<'_, '_, '_>) -> clean::Crate {
     Collapser.fold_crate(krate)
 }
 
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 42fa3e2006b42..67f291285c447 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -19,9 +19,11 @@ use crate::passes::{look_for_tests, Pass};
 
 use super::span_of_attrs;
 
-pub const COLLECT_INTRA_DOC_LINKS: Pass =
-    Pass::early("collect-intra-doc-links", collect_intra_doc_links,
-                "reads a crate's documentation to resolve intra-doc-links");
+pub const COLLECT_INTRA_DOC_LINKS: Pass = Pass {
+    name: "collect-intra-doc-links",
+    pass: collect_intra_doc_links,
+    description: "reads a crate's documentation to resolve intra-doc-links",
+};
 
 pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_, '_, '_>) -> Crate {
     if !UnstableFeatures::from_environment().is_nightly_build() {
diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs
index 903cce3bc032a..4c90540871d2e 100644
--- a/src/librustdoc/passes/collect_trait_impls.rs
+++ b/src/librustdoc/passes/collect_trait_impls.rs
@@ -6,9 +6,11 @@ use super::Pass;
 use rustc::util::nodemap::FxHashSet;
 use rustc::hir::def_id::DefId;
 
-pub const COLLECT_TRAIT_IMPLS: Pass =
-    Pass::early("collect-trait-impls", collect_trait_impls,
-                "retrieves trait impls for items in the crate");
+pub const COLLECT_TRAIT_IMPLS: Pass = Pass {
+    name: "collect-trait-impls",
+    pass: collect_trait_impls,
+    description: "retrieves trait impls for items in the crate",
+};
 
 pub fn collect_trait_impls(krate: Crate, cx: &DocContext<'_, '_, '_>) -> Crate {
     let mut synth = SyntheticImplCollector::new(cx);
diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs
index 4d7fef7a76a9f..3a9d8ef20ce84 100644
--- a/src/librustdoc/passes/mod.rs
+++ b/src/librustdoc/passes/mod.rs
@@ -6,7 +6,6 @@ use rustc::lint as lint;
 use rustc::middle::privacy::AccessLevels;
 use rustc::util::nodemap::DefIdSet;
 use std::mem;
-use std::fmt;
 use syntax::ast::NodeId;
 use syntax_pos::{DUMMY_SP, Span};
 use std::ops::Range;
@@ -46,84 +45,14 @@ pub use self::collect_trait_impls::COLLECT_TRAIT_IMPLS;
 mod check_code_block_syntax;
 pub use self::check_code_block_syntax::CHECK_CODE_BLOCK_SYNTAX;
 
-/// Represents a single pass.
+/// A single pass over the cleaned documentation.
+///
+/// Runs in the compiler context, so it has access to types and traits and the like.
 #[derive(Copy, Clone)]
-pub enum Pass {
-    /// An "early pass" is run in the compiler context, and can gather information about types and
-    /// traits and the like.
-    EarlyPass {
-        name: &'static str,
-        pass: fn(clean::Crate, &DocContext<'_, '_, '_>) -> clean::Crate,
-        description: &'static str,
-    },
-    /// A "late pass" is run between crate cleaning and page generation.
-    LatePass {
-        name: &'static str,
-        pass: fn(clean::Crate) -> clean::Crate,
-        description: &'static str,
-    },
-}
-
-impl fmt::Debug for Pass {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let mut dbg = match *self {
-            Pass::EarlyPass { .. } => f.debug_struct("EarlyPass"),
-            Pass::LatePass { .. } => f.debug_struct("LatePass"),
-        };
-
-        dbg.field("name", &self.name())
-           .field("pass", &"...")
-           .field("description", &self.description())
-           .finish()
-    }
-}
-
-impl Pass {
-    /// Constructs a new early pass.
-    pub const fn early(name: &'static str,
-                       pass: fn(clean::Crate, &DocContext<'_, '_, '_>) -> clean::Crate,
-                       description: &'static str) -> Pass {
-        Pass::EarlyPass { name, pass, description }
-    }
-
-    /// Constructs a new late pass.
-    pub const fn late(name: &'static str,
-                      pass: fn(clean::Crate) -> clean::Crate,
-                      description: &'static str) -> Pass {
-        Pass::LatePass { name, pass, description }
-    }
-
-    /// Returns the name of this pass.
-    pub fn name(self) -> &'static str {
-        match self {
-            Pass::EarlyPass { name, .. } |
-                Pass::LatePass { name, .. } => name,
-        }
-    }
-
-    /// Returns the description of this pass.
-    pub fn description(self) -> &'static str {
-        match self {
-            Pass::EarlyPass { description, .. } |
-                Pass::LatePass { description, .. } => description,
-        }
-    }
-
-    /// If this pass is an early pass, returns the pointer to its function.
-    pub fn early_fn(self) -> Option<fn(clean::Crate, &DocContext<'_, '_, '_>) -> clean::Crate> {
-        match self {
-            Pass::EarlyPass { pass, .. } => Some(pass),
-            _ => None,
-        }
-    }
-
-    /// If this pass is a late pass, returns the pointer to its function.
-    pub fn late_fn(self) -> Option<fn(clean::Crate) -> clean::Crate> {
-        match self {
-            Pass::LatePass { pass, .. } => Some(pass),
-            _ => None,
-        }
-    }
+pub struct Pass {
+    pub name: &'static str,
+    pub pass: fn(clean::Crate, &DocContext<'_, '_, '_>) -> clean::Crate,
+    pub description: &'static str,
 }
 
 /// The full list of passes.
@@ -141,27 +70,27 @@ pub const PASSES: &'static [Pass] = &[
 ];
 
 /// The list of passes run by default.
-pub const DEFAULT_PASSES: &'static [&'static str] = &[
+pub const DEFAULT_PASSES: &[&str] = &[
     "collect-trait-impls",
+    "collapse-docs",
+    "unindent-comments",
     "check-private-items-doc-tests",
     "strip-hidden",
     "strip-private",
     "collect-intra-doc-links",
     "check-code-block-syntax",
-    "collapse-docs",
-    "unindent-comments",
     "propagate-doc-cfg",
 ];
 
 /// The list of default passes run with `--document-private-items` is passed to rustdoc.
-pub const DEFAULT_PRIVATE_PASSES: &'static [&'static str] = &[
+pub const DEFAULT_PRIVATE_PASSES: &[&str] = &[
     "collect-trait-impls",
+    "collapse-docs",
+    "unindent-comments",
     "check-private-items-doc-tests",
     "strip-priv-imports",
     "collect-intra-doc-links",
     "check-code-block-syntax",
-    "collapse-docs",
-    "unindent-comments",
     "propagate-doc-cfg",
 ];
 
@@ -184,8 +113,8 @@ pub fn defaults(default_set: DefaultPassOption) -> &'static [&'static str] {
 }
 
 /// If the given name matches a known pass, returns its information.
-pub fn find_pass(pass_name: &str) -> Option<Pass> {
-    PASSES.iter().find(|p| p.name() == pass_name).cloned()
+pub fn find_pass(pass_name: &str) -> Option<&'static Pass> {
+    PASSES.iter().find(|p| p.name == pass_name)
 }
 
 struct Stripper<'a> {
@@ -438,11 +367,11 @@ crate fn source_span_for_markdown_range(
         .span_to_snippet(span_of_attrs(attrs))
         .ok()?;
 
-    let starting_line = markdown[..md_range.start].lines().count() - 1;
-    let ending_line = markdown[..md_range.end].lines().count() - 1;
+    let starting_line = markdown[..md_range.start].matches('\n').count();
+    let ending_line = starting_line + markdown[md_range.start..md_range.end].matches('\n').count();
 
-    // We use `split_terminator('\n')` instead of `lines()` when counting bytes so that we only
-    // we can treat CRLF and LF line endings the same way.
+    // We use `split_terminator('\n')` instead of `lines()` when counting bytes so that we treat
+    // CRLF and LF line endings the same way.
     let mut src_lines = snippet.split_terminator('\n');
     let md_lines = markdown.split_terminator('\n');
 
diff --git a/src/librustdoc/passes/private_items_doc_tests.rs b/src/librustdoc/passes/private_items_doc_tests.rs
index 819d15f65e8ef..1c3977c4f85cd 100644
--- a/src/librustdoc/passes/private_items_doc_tests.rs
+++ b/src/librustdoc/passes/private_items_doc_tests.rs
@@ -3,10 +3,11 @@ use crate::core::DocContext;
 use crate::fold::DocFolder;
 use crate::passes::{look_for_tests, Pass};
 
-
-pub const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass =
-    Pass::early("check-private-items-doc-tests", check_private_items_doc_tests,
-                "check private items doc tests");
+pub const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass = Pass {
+    name: "check-private-items-doc-tests",
+    pass: check_private_items_doc_tests,
+    description: "check private items doc tests",
+};
 
 struct PrivateItemDocTestLinter<'a, 'tcx: 'a, 'rcx: 'a> {
     cx: &'a DocContext<'a, 'tcx, 'rcx>,
diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs
index 9ba0b22728691..aed80b5ba86fd 100644
--- a/src/librustdoc/passes/propagate_doc_cfg.rs
+++ b/src/librustdoc/passes/propagate_doc_cfg.rs
@@ -2,14 +2,17 @@ use std::sync::Arc;
 
 use crate::clean::{Crate, Item};
 use crate::clean::cfg::Cfg;
+use crate::core::DocContext;
 use crate::fold::DocFolder;
 use crate::passes::Pass;
 
-pub const PROPAGATE_DOC_CFG: Pass =
-    Pass::late("propagate-doc-cfg", propagate_doc_cfg,
-        "propagates `#[doc(cfg(...))]` to child items");
+pub const PROPAGATE_DOC_CFG: Pass = Pass {
+    name: "propagate-doc-cfg",
+    pass: propagate_doc_cfg,
+    description: "propagates `#[doc(cfg(...))]` to child items",
+};
 
-pub fn propagate_doc_cfg(cr: Crate) -> Crate {
+pub fn propagate_doc_cfg(cr: Crate, _: &DocContext<'_, '_, '_>) -> Crate {
     CfgPropagator { parent_cfg: None }.fold_crate(cr)
 }
 
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index b3d50e06816c5..330057e53843b 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -7,9 +7,11 @@ use crate::core::DocContext;
 use crate::fold::{DocFolder, StripItem};
 use crate::passes::{ImplStripper, Pass};
 
-pub const STRIP_HIDDEN: Pass =
-    Pass::early("strip-hidden", strip_hidden,
-                "strips all doc(hidden) items from the output");
+pub const STRIP_HIDDEN: Pass = Pass {
+    name: "strip-hidden",
+    pass: strip_hidden,
+    description: "strips all doc(hidden) items from the output",
+};
 
 /// Strip items marked `#[doc(hidden)]`
 pub fn strip_hidden(krate: clean::Crate, _: &DocContext<'_, '_, '_>) -> clean::Crate {
diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs
index 3af1403e8749c..479f0877bd7d2 100644
--- a/src/librustdoc/passes/strip_priv_imports.rs
+++ b/src/librustdoc/passes/strip_priv_imports.rs
@@ -3,8 +3,11 @@ use crate::fold::{DocFolder};
 use crate::core::DocContext;
 use crate::passes::{ImportStripper, Pass};
 
-pub const STRIP_PRIV_IMPORTS: Pass = Pass::early("strip-priv-imports", strip_priv_imports,
-     "strips all private import statements (`use`, `extern crate`) from a crate");
+pub const STRIP_PRIV_IMPORTS: Pass = Pass {
+    name: "strip-priv-imports",
+    pass: strip_priv_imports,
+    description: "strips all private import statements (`use`, `extern crate`) from a crate",
+};
 
 pub fn strip_priv_imports(krate: clean::Crate, _: &DocContext<'_, '_, '_>)  -> clean::Crate {
     ImportStripper.fold_crate(krate)
diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs
index e553d792eb697..1ac3a90f38d35 100644
--- a/src/librustdoc/passes/strip_private.rs
+++ b/src/librustdoc/passes/strip_private.rs
@@ -5,10 +5,12 @@ use crate::fold::{DocFolder};
 use crate::core::DocContext;
 use crate::passes::{ImplStripper, ImportStripper, Stripper, Pass};
 
-pub const STRIP_PRIVATE: Pass =
-    Pass::early("strip-private", strip_private,
-        "strips all private items from a crate which cannot be seen externally, \
-         implies strip-priv-imports");
+pub const STRIP_PRIVATE: Pass = Pass {
+    name: "strip-private",
+    pass: strip_private,
+    description: "strips all private items from a crate which cannot be seen externally, \
+        implies strip-priv-imports",
+};
 
 /// Strip private items from the point of view of a crate or externally from a
 /// crate, specified by the `xcrate` flag.
diff --git a/src/librustdoc/passes/unindent_comments.rs b/src/librustdoc/passes/unindent_comments.rs
index 269e4cbe65f8b..b77cf68d7c63f 100644
--- a/src/librustdoc/passes/unindent_comments.rs
+++ b/src/librustdoc/passes/unindent_comments.rs
@@ -3,14 +3,17 @@ use std::string::String;
 use std::usize;
 
 use crate::clean::{self, DocFragment, Item};
+use crate::core::DocContext;
 use crate::fold::{self, DocFolder};
 use crate::passes::Pass;
 
-pub const UNINDENT_COMMENTS: Pass =
-    Pass::late("unindent-comments", unindent_comments,
-        "removes excess indentation on comments in order for markdown to like it");
+pub const UNINDENT_COMMENTS: Pass = Pass {
+    name: "unindent-comments",
+    pass: unindent_comments,
+    description: "removes excess indentation on comments in order for markdown to like it",
+};
 
-pub fn unindent_comments(krate: clean::Crate) -> clean::Crate {
+pub fn unindent_comments(krate: clean::Crate, _: &DocContext<'_, '_, '_>) -> clean::Crate {
     CommentCleaner.fold_crate(krate)
 }
 
diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs
index b634ea43e34e5..b49eea391772a 100644
--- a/src/libstd/io/mod.rs
+++ b/src/libstd/io/mod.rs
@@ -1458,7 +1458,7 @@ pub trait BufRead: Read {
     ///
     /// If successful, this function will return the total number of bytes read.
     ///
-    /// An empty buffer returned indicates that the stream has reached EOF.
+    /// If this function returns `Ok(0)`, the stream has reached EOF.
     ///
     /// # Errors
     ///
diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs
index 0324568e6fb52..00bd8e5e4de18 100644
--- a/src/libstd/io/stdio.rs
+++ b/src/libstd/io/stdio.rs
@@ -9,8 +9,8 @@ use sys::stdio;
 use sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
 use thread::LocalKey;
 
-/// Stdout used by print! and println! macros
 thread_local! {
+    /// Stdout used by print! and println! macros
     static LOCAL_STDOUT: RefCell<Option<Box<dyn Write + Send>>> = {
         RefCell::new(None)
     }
diff --git a/src/test/debuginfo/empty-string.rs b/src/test/debuginfo/empty-string.rs
new file mode 100644
index 0000000000000..43ce21908a958
--- /dev/null
+++ b/src/test/debuginfo/empty-string.rs
@@ -0,0 +1,33 @@
+// compile-flags:-g
+// min-gdb-version: 7.7
+// min-lldb-version: 310
+
+// === GDB TESTS ===================================================================================
+
+// gdb-command: run
+
+// gdb-command: print empty_string
+// gdb-check:$1 = ""
+
+// gdb-command: print empty_str
+// gdb-check:$2 = ""
+
+// === LLDB TESTS ==================================================================================
+
+// lldb-command: run
+
+// lldb-command: fr v empty_string
+// lldb-check:[...]empty_string = ""
+
+// lldb-command: fr v empty_str
+// lldb-check:[...]empty_str = ""
+
+fn main() {
+    let empty_string = String::new();
+
+    let empty_str = "";
+
+    zzz(); // #break
+}
+
+fn zzz() {}
diff --git a/src/test/rustdoc-ui/intra-links-warning.stderr b/src/test/rustdoc-ui/intra-links-warning.stderr
index e5409c042056c..60fc131dbda17 100644
--- a/src/test/rustdoc-ui/intra-links-warning.stderr
+++ b/src/test/rustdoc-ui/intra-links-warning.stderr
@@ -105,8 +105,8 @@ LL | | /// [error]
    |
    = note: the link appears in this line:
            
-            [error]
-             ^^^^^
+           [error]
+            ^^^^^
    = help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
 
 warning: `[error1]` cannot be resolved, ignoring it...
diff --git a/src/test/rustdoc-ui/issue-58473-2.rs b/src/test/rustdoc-ui/issue-58473-2.rs
new file mode 100644
index 0000000000000..5e5ddebe10884
--- /dev/null
+++ b/src/test/rustdoc-ui/issue-58473-2.rs
@@ -0,0 +1,12 @@
+// compile-pass
+
+#![deny(private_doc_tests)]
+
+mod foo {
+    /**
+    Does nothing, returns `()`
+
+    yadda-yadda-yadda
+    */
+    fn foo() {}
+}
diff --git a/src/test/rustdoc-ui/issue-58473.rs b/src/test/rustdoc-ui/issue-58473.rs
new file mode 100644
index 0000000000000..0e5be3292c053
--- /dev/null
+++ b/src/test/rustdoc-ui/issue-58473.rs
@@ -0,0 +1,10 @@
+// compile-pass
+
+pub trait Foo {
+    /**
+    Does nothing, returns `()`
+
+    yadda-yadda-yadda
+    */
+    fn foo() {}
+}
diff --git a/src/test/ui/useless_comment.rs b/src/test/ui/useless_comment.rs
index 531eec007fc48..7d2e5ab6f2b7f 100644
--- a/src/test/ui/useless_comment.rs
+++ b/src/test/ui/useless_comment.rs
@@ -1,18 +1,43 @@
+#![feature(stmt_expr_attributes)]
+
 #![deny(unused_doc_comments)]
 
+macro_rules! mac {
+    () => {}
+}
+
+/// foo //~ ERROR unused doc comment
+mac!();
+
 fn foo() {
-    /// a //~ ERROR doc comment not used by rustdoc
+    /// a //~ ERROR unused doc comment
     let x = 12;
 
-    /// b //~ doc comment not used by rustdoc
+    /// multi-line //~ unused doc comment
+    /// doc comment
+    /// that is unused
     match x {
-        /// c //~ ERROR doc comment not used by rustdoc
+        /// c //~ ERROR unused doc comment
         1 => {},
         _ => {}
     }
 
-    /// foo //~ ERROR doc comment not used by rustdoc
+    /// foo //~ ERROR unused doc comment
     unsafe {}
+
+    #[doc = "foo"] //~ ERROR unused doc comment
+    #[doc = "bar"] //~ ERROR unused doc comment
+    3;
+
+    /// bar //~ ERROR unused doc comment
+    mac!();
+
+    let x = /** comment */ 47; //~ ERROR unused doc comment
+
+    /// dox //~ ERROR unused doc comment
+    {
+
+    }
 }
 
 fn main() {
diff --git a/src/test/ui/useless_comment.stderr b/src/test/ui/useless_comment.stderr
index cc818f6ce7c39..0742a844b7f40 100644
--- a/src/test/ui/useless_comment.stderr
+++ b/src/test/ui/useless_comment.stderr
@@ -1,32 +1,98 @@
-error: doc comment not used by rustdoc
-  --> $DIR/useless_comment.rs:4:5
+error: unused doc comment
+  --> $DIR/useless_comment.rs:9:1
    |
-LL |     /// a //~ ERROR doc comment not used by rustdoc
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | /// foo //~ ERROR unused doc comment
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | mac!();
+   | ------- rustdoc does not generate documentation for macro expansions
    |
 note: lint level defined here
-  --> $DIR/useless_comment.rs:1:9
+  --> $DIR/useless_comment.rs:3:9
    |
 LL | #![deny(unused_doc_comments)]
    |         ^^^^^^^^^^^^^^^^^^^
+   = help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion
 
-error: doc comment not used by rustdoc
-  --> $DIR/useless_comment.rs:7:5
+error: unused doc comment
+  --> $DIR/useless_comment.rs:13:5
    |
-LL |     /// b //~ doc comment not used by rustdoc
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     /// a //~ ERROR unused doc comment
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     let x = 12;
+   |     ----------- rustdoc does not generate documentation for statements
 
-error: doc comment not used by rustdoc
-  --> $DIR/useless_comment.rs:9:9
+error: unused doc comment
+  --> $DIR/useless_comment.rs:16:5
    |
-LL |         /// c //~ ERROR doc comment not used by rustdoc
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | /     /// multi-line //~ unused doc comment
+LL | |     /// doc comment
+LL | |     /// that is unused
+   | |______________________^
+LL | /     match x {
+LL | |         /// c //~ ERROR unused doc comment
+LL | |         1 => {},
+LL | |         _ => {}
+LL | |     }
+   | |_____- rustdoc does not generate documentation for expressions
 
-error: doc comment not used by rustdoc
-  --> $DIR/useless_comment.rs:14:5
+error: unused doc comment
+  --> $DIR/useless_comment.rs:20:9
    |
-LL |     /// foo //~ ERROR doc comment not used by rustdoc
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |         /// c //~ ERROR unused doc comment
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |         1 => {},
+   |         ------- rustdoc does not generate documentation for match arms
 
-error: aborting due to 4 previous errors
+error: unused doc comment
+  --> $DIR/useless_comment.rs:25:5
+   |
+LL |     /// foo //~ ERROR unused doc comment
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     unsafe {}
+   |     --------- rustdoc does not generate documentation for expressions
+
+error: unused doc comment
+  --> $DIR/useless_comment.rs:28:5
+   |
+LL |     #[doc = "foo"] //~ ERROR unused doc comment
+   |     ^^^^^^^^^^^^^^
+LL |     #[doc = "bar"] //~ ERROR unused doc comment
+LL |     3;
+   |     - rustdoc does not generate documentation for expressions
+
+error: unused doc comment
+  --> $DIR/useless_comment.rs:29:5
+   |
+LL |     #[doc = "bar"] //~ ERROR unused doc comment
+   |     ^^^^^^^^^^^^^^
+LL |     3;
+   |     - rustdoc does not generate documentation for expressions
+
+error: unused doc comment
+  --> $DIR/useless_comment.rs:32:5
+   |
+LL |     /// bar //~ ERROR unused doc comment
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     mac!();
+   |     ------- rustdoc does not generate documentation for macro expansions
+   |
+   = help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion
+
+error: unused doc comment
+  --> $DIR/useless_comment.rs:35:13
+   |
+LL |     let x = /** comment */ 47; //~ ERROR unused doc comment
+   |             ^^^^^^^^^^^^^^ -- rustdoc does not generate documentation for expressions
+
+error: unused doc comment
+  --> $DIR/useless_comment.rs:37:5
+   |
+LL |       /// dox //~ ERROR unused doc comment
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | /     {
+LL | |
+LL | |     }
+   | |_____- rustdoc does not generate documentation for expressions
+
+error: aborting due to 10 previous errors