about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs767
-rw-r--r--src/test/rustdoc-ui/assoc-item-not-in-scope.stderr3
-rw-r--r--src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr2
-rw-r--r--src/test/rustdoc-ui/intra-doc-alias-ice.stderr3
-rw-r--r--src/test/rustdoc-ui/intra-link-errors.rs88
-rw-r--r--src/test/rustdoc-ui/intra-link-errors.stderr116
-rw-r--r--src/test/rustdoc-ui/intra-link-prim-conflict.rs4
-rw-r--r--src/test/rustdoc-ui/intra-link-prim-conflict.stderr20
-rw-r--r--src/test/rustdoc-ui/intra-link-span-ice-55723.stderr2
-rw-r--r--src/test/rustdoc-ui/intra-links-ambiguity.stderr12
-rw-r--r--src/test/rustdoc-ui/intra-links-anchors.stderr6
-rw-r--r--src/test/rustdoc-ui/intra-links-disambiguator-mismatch.rs18
-rw-r--r--src/test/rustdoc-ui/intra-links-disambiguator-mismatch.stderr54
-rw-r--r--src/test/rustdoc-ui/intra-links-warning-crlf.stderr8
-rw-r--r--src/test/rustdoc-ui/intra-links-warning.stderr44
-rw-r--r--src/test/rustdoc-ui/lint-group.stderr2
16 files changed, 867 insertions, 282 deletions
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 5d10e2e149b..5780610c862 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -17,8 +17,9 @@ use rustc_span::hygiene::MacroKind;
 use rustc_span::symbol::Ident;
 use rustc_span::symbol::Symbol;
 use rustc_span::DUMMY_SP;
-use smallvec::SmallVec;
+use smallvec::{smallvec, SmallVec};
 
+use std::borrow::Cow;
 use std::cell::Cell;
 use std::ops::Range;
 
@@ -46,19 +47,73 @@ pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
     }
 }
 
-enum ErrorKind {
-    ResolutionFailure,
+enum ErrorKind<'a> {
+    Resolve(Box<ResolutionFailure<'a>>),
     AnchorFailure(AnchorFailure),
 }
 
+impl<'a> From<ResolutionFailure<'a>> for ErrorKind<'a> {
+    fn from(err: ResolutionFailure<'a>) -> Self {
+        ErrorKind::Resolve(box err)
+    }
+}
+
+#[derive(Debug)]
+enum ResolutionFailure<'a> {
+    /// This resolved, but with the wrong namespace.
+    /// `Namespace` is the expected namespace (as opposed to the actual).
+    WrongNamespace(Res, Namespace),
+    /// This has a partial resolution, but is not in the TypeNS and so cannot
+    /// have associated items or fields.
+    CannotHaveAssociatedItems(Res, Namespace),
+    /// `name` is the base name of the path (not necessarily the whole link)
+    NotInScope { module_id: DefId, name: Cow<'a, str> },
+    /// this is a primitive type without an impls (no associated methods)
+    /// when will this actually happen?
+    /// the `Res` is the primitive it resolved to
+    NoPrimitiveImpl(Res, String),
+    /// `[u8::not_found]`
+    /// the `Res` is the primitive it resolved to
+    NoPrimitiveAssocItem { res: Res, prim_name: &'a str, assoc_item: Symbol },
+    /// `[S::not_found]`
+    /// the `String` is the associated item that wasn't found
+    NoAssocItem(Res, Symbol),
+    /// should not ever happen
+    NoParentItem,
+    /// this could be an enum variant, but the last path fragment wasn't resolved.
+    /// the `String` is the variant that didn't exist
+    NotAVariant(Res, Symbol),
+    /// used to communicate that this should be ignored, but shouldn't be reported to the user
+    Dummy,
+}
+
+impl ResolutionFailure<'a> {
+    // A partial or full resolution
+    fn res(&self) -> Option<Res> {
+        use ResolutionFailure::*;
+        match self {
+            NoPrimitiveAssocItem { res, .. }
+            | NoAssocItem(res, _)
+            | NoPrimitiveImpl(res, _)
+            | NotAVariant(res, _)
+            | WrongNamespace(res, _)
+            | CannotHaveAssociatedItems(res, _) => Some(*res),
+            NotInScope { .. } | NoParentItem | Dummy => None,
+        }
+    }
+
+    // This resolved fully (not just partially) but is erroneous for some other reason
+    fn full_res(&self) -> Option<Res> {
+        match self {
+            Self::WrongNamespace(res, _) => Some(*res),
+            _ => None,
+        }
+    }
+}
+
 enum AnchorFailure {
     MultipleAnchors,
-    Primitive,
-    Variant,
-    AssocConstant,
-    AssocType,
-    Field,
-    Method,
+    RustdocAnchorConflict(Res),
 }
 
 struct LinkCollector<'a, 'tcx> {
@@ -68,7 +123,7 @@ struct LinkCollector<'a, 'tcx> {
     /// This is used to store the kind of associated items,
     /// because `clean` and the disambiguator code expect them to be different.
     /// See the code for associated items on inherent impls for details.
-    kind_side_channel: Cell<Option<DefKind>>,
+    kind_side_channel: Cell<Option<(DefKind, DefId)>>,
 }
 
 impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
@@ -78,17 +133,25 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
 
     fn variant_field(
         &self,
-        path_str: &str,
+        path_str: &'path str,
         current_item: &Option<String>,
         module_id: DefId,
-    ) -> Result<(Res, Option<String>), ErrorKind> {
+        extra_fragment: &Option<String>,
+    ) -> Result<(Res, Option<String>), ErrorKind<'path>> {
         let cx = self.cx;
 
+        debug!("looking for enum variant {}", path_str);
         let mut split = path_str.rsplitn(3, "::");
-        let variant_field_name =
-            split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?;
+        let variant_field_name = split
+            .next()
+            .map(|f| Symbol::intern(f))
+            .expect("fold_item should ensure link is non-empty");
         let variant_name =
-            split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?;
+            // we're not sure this is a variant at all, so use the full string
+            split.next().map(|f| Symbol::intern(f)).ok_or_else(|| ResolutionFailure::NotInScope {
+                module_id,
+                name: path_str.into(),
+            })?;
         let path = split
             .next()
             .map(|f| {
@@ -99,14 +162,18 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 }
                 f.to_owned()
             })
-            .ok_or(ErrorKind::ResolutionFailure)?;
-        let (_, ty_res) = cx
+            .ok_or_else(|| ResolutionFailure::NotInScope {
+                module_id,
+                name: variant_name.to_string().into(),
+            })?;
+        let ty_res = cx
             .enter_resolver(|resolver| {
                 resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
             })
-            .map_err(|_| ErrorKind::ResolutionFailure)?;
+            .map(|(_, res)| res)
+            .unwrap_or(Res::Err);
         if let Res::Err = ty_res {
-            return Err(ErrorKind::ResolutionFailure);
+            return Err(ResolutionFailure::NotInScope { module_id, name: path.into() }.into());
         }
         let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
         match ty_res {
@@ -118,7 +185,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                     .flat_map(|imp| cx.tcx.associated_items(*imp).in_definition_order())
                     .any(|item| item.ident.name == variant_name)
                 {
-                    return Err(ErrorKind::ResolutionFailure);
+                    // This is just to let `fold_item` know that this shouldn't be considered;
+                    // it's a bug for the error to make it to the user
+                    return Err(ResolutionFailure::Dummy.into());
                 }
                 match cx.tcx.type_of(did).kind() {
                     ty::Adt(def, _) if def.is_enum() => {
@@ -131,18 +200,43 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                                 )),
                             ))
                         } else {
-                            Err(ErrorKind::ResolutionFailure)
+                            Err(ResolutionFailure::NotAVariant(ty_res, variant_field_name).into())
                         }
                     }
-                    _ => Err(ErrorKind::ResolutionFailure),
+                    _ => unreachable!(),
                 }
             }
-            _ => Err(ErrorKind::ResolutionFailure),
+            // `variant_field` looks at 3 different path segments in a row.
+            // But `NoAssocItem` assumes there are only 2. Check to see if there's
+            // an intermediate segment that resolves.
+            _ => {
+                let intermediate_path = format!("{}::{}", path, variant_name);
+                // NOTE: we have to be careful here, because we're already in `resolve`.
+                // We know this doesn't recurse forever because we use a shorter path each time.
+                // NOTE: this uses `TypeNS` because nothing else has a valid path segment after
+                let kind = if let Some(intermediate) = self.check_full_res(
+                    TypeNS,
+                    &intermediate_path,
+                    Some(module_id),
+                    current_item,
+                    extra_fragment,
+                ) {
+                    ResolutionFailure::NoAssocItem(intermediate, variant_field_name)
+                } else {
+                    // Even with the shorter path, it didn't resolve, so say that.
+                    ResolutionFailure::NoAssocItem(ty_res, variant_name)
+                };
+                Err(kind.into())
+            }
         }
     }
 
     /// Resolves a string as a macro.
-    fn macro_resolve(&self, path_str: &str, parent_id: Option<DefId>) -> Option<Res> {
+    fn macro_resolve(
+        &self,
+        path_str: &'a str,
+        parent_id: Option<DefId>,
+    ) -> Result<Res, ResolutionFailure<'a>> {
         let cx = self.cx;
         let path = ast::Path::from_ident(Ident::from_str(path_str));
         cx.enter_resolver(|resolver| {
@@ -154,11 +248,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 false,
             ) {
                 if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind {
-                    return Some(res.map_id(|_| panic!("unexpected id")));
+                    return Some(Ok(res.map_id(|_| panic!("unexpected id"))));
                 }
             }
             if let Some(res) = resolver.all_macros().get(&Symbol::intern(path_str)) {
-                return Some(res.map_id(|_| panic!("unexpected id")));
+                return Some(Ok(res.map_id(|_| panic!("unexpected id"))));
             }
             if let Some(module_id) = parent_id {
                 debug!("resolving {} as a macro in the module {:?}", path_str, module_id);
@@ -168,25 +262,47 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                     // don't resolve builtins like `#[derive]`
                     if let Res::Def(..) = res {
                         let res = res.map_id(|_| panic!("unexpected node_id"));
-                        return Some(res);
+                        return Some(Ok(res));
                     }
                 }
             } else {
                 debug!("attempting to resolve item without parent module: {}", path_str);
+                return Some(Err(ResolutionFailure::NoParentItem));
             }
             None
         })
+        // This weird control flow is so we don't borrow the resolver more than once at a time
+        .unwrap_or_else(|| {
+            let mut split = path_str.rsplitn(2, "::");
+            if let Some((parent, base)) = split.next().and_then(|x| Some((split.next()?, x))) {
+                if let Some(res) = self.check_full_res(TypeNS, parent, parent_id, &None, &None) {
+                    return Err(if matches!(res, Res::PrimTy(_)) {
+                        ResolutionFailure::NoPrimitiveAssocItem {
+                            res,
+                            prim_name: parent,
+                            assoc_item: Symbol::intern(base),
+                        }
+                    } else {
+                        ResolutionFailure::NoAssocItem(res, Symbol::intern(base))
+                    });
+                }
+            }
+            Err(ResolutionFailure::NotInScope {
+                module_id: parent_id.expect("already saw `Some` when resolving as a macro"),
+                name: path_str.into(),
+            })
+        })
     }
     /// Resolves a string as a path within a particular namespace. Also returns an optional
     /// URL fragment in the case of variants and methods.
-    fn resolve(
+    fn resolve<'path>(
         &self,
-        path_str: &str,
+        path_str: &'path str,
         ns: Namespace,
         current_item: &Option<String>,
         parent_id: Option<DefId>,
         extra_fragment: &Option<String>,
-    ) -> Result<(Res, Option<String>), ErrorKind> {
+    ) -> Result<(Res, Option<String>), ErrorKind<'path>> {
         let cx = self.cx;
 
         // In case we're in a module, try to resolve the relative path.
@@ -196,8 +312,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
             });
             debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
             let result = match result {
-                Ok((_, Res::Err)) => Err(ErrorKind::ResolutionFailure),
-                _ => result.map_err(|_| ErrorKind::ResolutionFailure),
+                Ok((_, Res::Err)) => Err(()),
+                x => x,
             };
 
             if let Ok((_, res)) = result {
@@ -213,7 +329,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                     // Not a trait item; just return what we found.
                     Res::PrimTy(..) => {
                         if extra_fragment.is_some() {
-                            return Err(ErrorKind::AnchorFailure(AnchorFailure::Primitive));
+                            return Err(ErrorKind::AnchorFailure(
+                                AnchorFailure::RustdocAnchorConflict(res),
+                            ));
                         }
                         return Ok((res, Some(path_str.to_owned())));
                     }
@@ -226,20 +344,22 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 };
 
                 if value != (ns == ValueNS) {
-                    return Err(ErrorKind::ResolutionFailure);
+                    return Err(ResolutionFailure::WrongNamespace(res, ns).into());
                 }
             } else if let Some((path, prim)) = is_primitive(path_str, ns) {
                 if extra_fragment.is_some() {
-                    return Err(ErrorKind::AnchorFailure(AnchorFailure::Primitive));
+                    return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(
+                        prim,
+                    )));
                 }
                 return Ok((prim, Some(path.to_owned())));
             }
 
             // Try looking for methods and associated items.
             let mut split = path_str.rsplitn(2, "::");
-            let item_name =
-                split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::ResolutionFailure)?;
-            let path = split
+            // this can be an `unwrap()` because we ensure the link is never empty
+            let item_name = Symbol::intern(split.next().unwrap());
+            let path_root = split
                 .next()
                 .map(|f| {
                     if f == "self" || f == "Self" {
@@ -249,10 +369,17 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                     }
                     f.to_owned()
                 })
-                .ok_or(ErrorKind::ResolutionFailure)?;
-
-            if let Some((path, prim)) = is_primitive(&path, TypeNS) {
-                for &impl_ in primitive_impl(cx, &path).ok_or(ErrorKind::ResolutionFailure)? {
+                // If there's no `::`, it's not an associated item.
+                // So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
+                .ok_or_else(|| {
+                    debug!("found no `::`, assumming {} was correctly not in scope", item_name);
+                    ResolutionFailure::NotInScope { module_id, name: item_name.to_string().into() }
+                })?;
+
+            if let Some((path, prim)) = is_primitive(&path_root, TypeNS) {
+                let impls = primitive_impl(cx, &path)
+                    .ok_or_else(|| ResolutionFailure::NoPrimitiveImpl(prim, path_root.into()))?;
+                for &impl_ in impls {
                     let link = cx
                         .tcx
                         .associated_items(impl_)
@@ -272,21 +399,54 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         return Ok(link);
                     }
                 }
-                return Err(ErrorKind::ResolutionFailure);
+                debug!(
+                    "returning primitive error for {}::{} in {} namespace",
+                    path,
+                    item_name,
+                    ns.descr()
+                );
+                return Err(ResolutionFailure::NoPrimitiveAssocItem {
+                    res: prim,
+                    prim_name: path,
+                    assoc_item: item_name,
+                }
+                .into());
             }
 
-            let (_, ty_res) = cx
+            let ty_res = cx
                 .enter_resolver(|resolver| {
-                    resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
+                    // only types can have associated items
+                    resolver.resolve_str_path_error(DUMMY_SP, &path_root, TypeNS, module_id)
                 })
-                .map_err(|_| ErrorKind::ResolutionFailure)?;
-            if let Res::Err = ty_res {
-                return if ns == Namespace::ValueNS {
-                    self.variant_field(path_str, current_item, module_id)
-                } else {
-                    Err(ErrorKind::ResolutionFailure)
-                };
-            }
+                .map(|(_, res)| res);
+            let ty_res = match ty_res {
+                Err(()) | Ok(Res::Err) => {
+                    return if ns == Namespace::ValueNS {
+                        self.variant_field(path_str, current_item, module_id, extra_fragment)
+                    } else {
+                        // See if it only broke because of the namespace.
+                        let kind = cx.enter_resolver(|resolver| {
+                            // NOTE: this doesn't use `check_full_res` because we explicitly want to ignore `TypeNS` (we already checked it)
+                            for &ns in &[MacroNS, ValueNS] {
+                                match resolver
+                                    .resolve_str_path_error(DUMMY_SP, &path_root, ns, module_id)
+                                {
+                                    Ok((_, Res::Err)) | Err(()) => {}
+                                    Ok((_, res)) => {
+                                        let res = res.map_id(|_| panic!("unexpected node_id"));
+                                        return ResolutionFailure::CannotHaveAssociatedItems(
+                                            res, ns,
+                                        );
+                                    }
+                                }
+                            }
+                            ResolutionFailure::NotInScope { module_id, name: path_root.into() }
+                        });
+                        Err(kind.into())
+                    };
+                }
+                Ok(res) => res,
+            };
             let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
             let res = match ty_res {
                 Res::Def(
@@ -295,7 +455,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 ) => {
                     debug!("looking for associated item named {} for item {:?}", item_name, did);
                     // Checks if item_name belongs to `impl SomeItem`
-                    let kind = cx
+                    let assoc_item = cx
                         .tcx
                         .inherent_impls(did)
                         .iter()
@@ -307,7 +467,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                                 imp,
                             )
                         })
-                        .map(|item| item.kind)
+                        .map(|item| (item.kind, item.def_id))
                         // There should only ever be one associated item that matches from any inherent impl
                         .next()
                         // Check if item_name belongs to `impl SomeTrait for SomeItem`
@@ -323,26 +483,25 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                             kind
                         });
 
-                    if let Some(kind) = kind {
+                    if let Some((kind, id)) = assoc_item {
                         let out = match kind {
                             ty::AssocKind::Fn => "method",
                             ty::AssocKind::Const => "associatedconstant",
                             ty::AssocKind::Type => "associatedtype",
                         };
                         Some(if extra_fragment.is_some() {
-                            Err(ErrorKind::AnchorFailure(if kind == ty::AssocKind::Fn {
-                                AnchorFailure::Method
-                            } else {
-                                AnchorFailure::AssocConstant
-                            }))
+                            Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(
+                                ty_res,
+                            )))
                         } else {
                             // HACK(jynelson): `clean` expects the type, not the associated item.
                             // but the disambiguator logic expects the associated item.
                             // Store the kind in a side channel so that only the disambiguator logic looks at it.
-                            self.kind_side_channel.set(Some(kind.as_def_kind()));
+                            self.kind_side_channel.set(Some((kind.as_def_kind(), id)));
                             Ok((ty_res, Some(format!("{}.{}", out, item_name))))
                         })
                     } else if ns == Namespace::ValueNS {
+                        debug!("looking for variants or fields named {} for {:?}", item_name, did);
                         match cx.tcx.type_of(did).kind() {
                             ty::Adt(def, _) => {
                                 let field = if def.is_enum() {
@@ -355,11 +514,17 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                                 };
                                 field.map(|item| {
                                     if extra_fragment.is_some() {
-                                        Err(ErrorKind::AnchorFailure(if def.is_enum() {
-                                            AnchorFailure::Variant
-                                        } else {
-                                            AnchorFailure::Field
-                                        }))
+                                        let res = Res::Def(
+                                            if def.is_enum() {
+                                                DefKind::Variant
+                                            } else {
+                                                DefKind::Field
+                                            },
+                                            item.did,
+                                        );
+                                        Err(ErrorKind::AnchorFailure(
+                                            AnchorFailure::RustdocAnchorConflict(res),
+                                        ))
                                     } else {
                                         Ok((
                                             ty_res,
@@ -380,7 +545,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         }
                     } else {
                         // We already know this isn't in ValueNS, so no need to check variant_field
-                        return Err(ErrorKind::ResolutionFailure);
+                        return Err(ResolutionFailure::NoAssocItem(ty_res, item_name).into());
                     }
                 }
                 Res::Def(DefKind::Trait, did) => cx
@@ -401,13 +566,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         };
 
                         if extra_fragment.is_some() {
-                            Err(ErrorKind::AnchorFailure(if item.kind == ty::AssocKind::Const {
-                                AnchorFailure::AssocConstant
-                            } else if item.kind == ty::AssocKind::Type {
-                                AnchorFailure::AssocType
-                            } else {
-                                AnchorFailure::Method
-                            }))
+                            Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(
+                                ty_res,
+                            )))
                         } else {
                             let res = Res::Def(item.kind.as_def_kind(), item.def_id);
                             Ok((res, Some(format!("{}.{}", kind, item_name))))
@@ -417,14 +578,54 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
             };
             res.unwrap_or_else(|| {
                 if ns == Namespace::ValueNS {
-                    self.variant_field(path_str, current_item, module_id)
+                    self.variant_field(path_str, current_item, module_id, extra_fragment)
                 } else {
-                    Err(ErrorKind::ResolutionFailure)
+                    Err(ResolutionFailure::NoAssocItem(ty_res, item_name).into())
                 }
             })
         } else {
             debug!("attempting to resolve item without parent module: {}", path_str);
-            Err(ErrorKind::ResolutionFailure)
+            Err(ResolutionFailure::NoParentItem.into())
+        }
+    }
+
+    /// Used for reporting better errors.
+    ///
+    /// Returns whether the link resolved 'fully' in another namespace.
+    /// 'fully' here means that all parts of the link resolved, not just some path segments.
+    /// This returns the `Res` even if it was erroneous for some reason
+    /// (such as having invalid URL fragments or being in the wrong namespace).
+    fn check_full_res(
+        &self,
+        ns: Namespace,
+        path_str: &str,
+        base_node: Option<DefId>,
+        current_item: &Option<String>,
+        extra_fragment: &Option<String>,
+    ) -> Option<Res> {
+        let check_full_res_inner = |this: &Self, result: Result<Res, ErrorKind<'_>>| {
+            let res = match result {
+                Ok(res) => Some(res),
+                Err(ErrorKind::Resolve(box kind)) => kind.full_res(),
+                Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res))) => {
+                    Some(res)
+                }
+                Err(ErrorKind::AnchorFailure(AnchorFailure::MultipleAnchors)) => None,
+            };
+            this.kind_side_channel.take().map(|(kind, id)| Res::Def(kind, id)).or(res)
+        };
+        // cannot be used for macro namespace
+        let check_full_res = |this: &Self, ns| {
+            let result = this.resolve(path_str, ns, current_item, base_node, extra_fragment);
+            check_full_res_inner(this, result.map(|(res, _)| res))
+        };
+        let check_full_res_macro = |this: &Self| {
+            let result = this.macro_resolve(path_str, base_node);
+            check_full_res_inner(this, result.map_err(ErrorKind::from))
+        };
+        match ns {
+            Namespace::MacroNS => check_full_res_macro(self),
+            Namespace::TypeNS | Namespace::ValueNS => check_full_res(self, ns),
         }
     }
 }
@@ -435,7 +636,7 @@ fn resolve_associated_trait_item(
     item_name: Symbol,
     ns: Namespace,
     cx: &DocContext<'_>,
-) -> Option<ty::AssocKind> {
+) -> Option<(ty::AssocKind, DefId)> {
     let ty = cx.tcx.type_of(did);
     // First consider automatic impls: `impl From<T> for T`
     let implicit_impls = crate::clean::get_auto_trait_and_blanket_impls(cx, ty, did);
@@ -463,7 +664,7 @@ fn resolve_associated_trait_item(
                             // but provided methods come directly from `tcx`.
                             // Fortunately, we don't need the whole method, we just need to know
                             // what kind of associated item it is.
-                            Some((assoc.def_id, kind))
+                            Some((kind, assoc.def_id))
                         });
                         let assoc = items.next();
                         debug_assert_eq!(items.count(), 0);
@@ -485,7 +686,7 @@ fn resolve_associated_trait_item(
                                 ns,
                                 trait_,
                             )
-                            .map(|assoc| (assoc.def_id, assoc.kind))
+                            .map(|assoc| (assoc.kind, assoc.def_id))
                     }
                 }
                 _ => panic!("get_impls returned something that wasn't an impl"),
@@ -502,12 +703,12 @@ fn resolve_associated_trait_item(
             cx.tcx
                 .associated_items(trait_)
                 .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_)
-                .map(|assoc| (assoc.def_id, assoc.kind))
+                .map(|assoc| (assoc.kind, assoc.def_id))
         }));
     }
     // FIXME: warn about ambiguity
     debug!("the candidates were {:?}", candidates);
-    candidates.pop().map(|(_, kind)| kind)
+    candidates.pop()
 }
 
 /// Given a type, return all traits in scope in `module` implemented by that type.
@@ -536,7 +737,7 @@ fn traits_implemented_by(cx: &DocContext<'_>, type_: DefId, module: DefId) -> Fx
             let trait_ref = cx.tcx.impl_trait_ref(impl_).expect("this is not an inherent impl");
             // Check if these are the same type.
             let impl_type = trait_ref.self_ty();
-            debug!(
+            trace!(
                 "comparing type {} with kind {:?} against type {:?}",
                 impl_type,
                 impl_type.kind(),
@@ -562,10 +763,10 @@ fn traits_implemented_by(cx: &DocContext<'_>, type_: DefId, module: DefId) -> Fx
 /// Check for resolve collisions between a trait and its derive
 ///
 /// These are common and we should just resolve to the trait in that case
-fn is_derive_trait_collision<T>(ns: &PerNS<Option<(Res, T)>>) -> bool {
+fn is_derive_trait_collision<T>(ns: &PerNS<Result<(Res, T), ResolutionFailure<'_>>>) -> bool {
     if let PerNS {
-        type_ns: Some((Res::Def(DefKind::Trait, _), _)),
-        macro_ns: Some((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)),
+        type_ns: Ok((Res::Def(DefKind::Trait, _), _)),
+        macro_ns: Ok((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)),
         ..
     } = *ns
     {
@@ -764,8 +965,32 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                         match self.resolve(path_str, ns, &current_item, base_node, &extra_fragment)
                         {
                             Ok(res) => res,
-                            Err(ErrorKind::ResolutionFailure) => {
-                                resolution_failure(cx, &item, path_str, &dox, link_range);
+                            Err(ErrorKind::Resolve(box mut kind)) => {
+                                // We only looked in one namespace. Try to give a better error if possible.
+                                if kind.full_res().is_none() {
+                                    let other_ns = if ns == ValueNS { TypeNS } else { ValueNS };
+                                    for &new_ns in &[other_ns, MacroNS] {
+                                        if let Some(res) = self.check_full_res(
+                                            new_ns,
+                                            path_str,
+                                            base_node,
+                                            &current_item,
+                                            &extra_fragment,
+                                        ) {
+                                            kind = ResolutionFailure::WrongNamespace(res, ns);
+                                            break;
+                                        }
+                                    }
+                                }
+                                resolution_failure(
+                                    self,
+                                    &item,
+                                    path_str,
+                                    disambiguator,
+                                    &dox,
+                                    link_range,
+                                    smallvec![kind],
+                                );
                                 // This could just be a normal link or a broken link
                                 // we could potentially check if something is
                                 // "intra-doc-link-like" and warn in that case.
@@ -792,13 +1017,13 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                             ) {
                                 Ok(res) => {
                                     debug!("got res in TypeNS: {:?}", res);
-                                    Some(res)
+                                    Ok(res)
                                 }
                                 Err(ErrorKind::AnchorFailure(msg)) => {
                                     anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
                                     continue;
                                 }
-                                Err(ErrorKind::ResolutionFailure) => None,
+                                Err(ErrorKind::Resolve(box kind)) => Err(kind),
                             },
                             value_ns: match self.resolve(
                                 path_str,
@@ -807,48 +1032,57 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                                 base_node,
                                 &extra_fragment,
                             ) {
-                                Ok(res) => Some(res),
+                                Ok(res) => Ok(res),
                                 Err(ErrorKind::AnchorFailure(msg)) => {
                                     anchor_failure(cx, &item, &ori_link, &dox, link_range, msg);
                                     continue;
                                 }
-                                Err(ErrorKind::ResolutionFailure) => None,
+                                Err(ErrorKind::Resolve(box kind)) => Err(kind),
                             }
                             .and_then(|(res, fragment)| {
                                 // Constructors are picked up in the type namespace.
                                 match res {
-                                    Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => None,
+                                    Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => {
+                                        Err(ResolutionFailure::WrongNamespace(res, TypeNS))
+                                    }
                                     _ => match (fragment, extra_fragment) {
                                         (Some(fragment), Some(_)) => {
                                             // Shouldn't happen but who knows?
-                                            Some((res, Some(fragment)))
-                                        }
-                                        (fragment, None) | (None, fragment) => {
-                                            Some((res, fragment))
+                                            Ok((res, Some(fragment)))
                                         }
+                                        (fragment, None) | (None, fragment) => Ok((res, fragment)),
                                     },
                                 }
                             }),
                         };
 
-                        if candidates.is_empty() {
-                            resolution_failure(cx, &item, path_str, &dox, link_range);
+                        let len = candidates.iter().filter(|res| res.is_ok()).count();
+
+                        if len == 0 {
+                            resolution_failure(
+                                self,
+                                &item,
+                                path_str,
+                                disambiguator,
+                                &dox,
+                                link_range,
+                                candidates.into_iter().filter_map(|res| res.err()).collect(),
+                            );
                             // this could just be a normal link
                             continue;
                         }
 
-                        let len = candidates.clone().present_items().count();
-
                         if len == 1 {
-                            candidates.present_items().next().unwrap()
+                            candidates.into_iter().filter_map(|res| res.ok()).next().unwrap()
                         } else if len == 2 && is_derive_trait_collision(&candidates) {
                             candidates.type_ns.unwrap()
                         } else {
                             if is_derive_trait_collision(&candidates) {
-                                candidates.macro_ns = None;
+                                candidates.macro_ns = Err(ResolutionFailure::Dummy);
                             }
+                            // If we're reporting an ambiguity, don't mention the namespaces that failed
                             let candidates =
-                                candidates.map(|candidate| candidate.map(|(res, _)| res));
+                                candidates.map(|candidate| candidate.ok().map(|(res, _)| res));
                             ambiguity_error(
                                 cx,
                                 &item,
@@ -861,11 +1095,33 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                         }
                     }
                     Some(MacroNS) => {
-                        if let Some(res) = self.macro_resolve(path_str, base_node) {
-                            (res, extra_fragment)
-                        } else {
-                            resolution_failure(cx, &item, path_str, &dox, link_range);
-                            continue;
+                        match self.macro_resolve(path_str, base_node) {
+                            Ok(res) => (res, extra_fragment),
+                            Err(mut kind) => {
+                                // `macro_resolve` only looks in the macro namespace. Try to give a better error if possible.
+                                for &ns in &[TypeNS, ValueNS] {
+                                    if let Some(res) = self.check_full_res(
+                                        ns,
+                                        path_str,
+                                        base_node,
+                                        &current_item,
+                                        &extra_fragment,
+                                    ) {
+                                        kind = ResolutionFailure::WrongNamespace(res, MacroNS);
+                                        break;
+                                    }
+                                }
+                                resolution_failure(
+                                    self,
+                                    &item,
+                                    path_str,
+                                    disambiguator,
+                                    &dox,
+                                    link_range,
+                                    smallvec![kind],
+                                );
+                                continue;
+                            }
                         }
                     }
                 }
@@ -889,7 +1145,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                                 path_str,
                                 &dox,
                                 link_range,
-                                AnchorFailure::Primitive,
+                                AnchorFailure::RustdocAnchorConflict(prim),
                             );
                             continue;
                         }
@@ -907,7 +1163,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
             let report_mismatch = |specified: Disambiguator, resolved: Disambiguator| {
                 // The resolved item did not match the disambiguator; give a better error than 'not found'
                 let msg = format!("incompatible link kind for `{}`", path_str);
-                report_diagnostic(cx, &msg, &item, &dox, link_range.clone(), |diag, sp| {
+                report_diagnostic(cx, &msg, &item, &dox, &link_range, |diag, sp| {
                     let note = format!(
                         "this link resolved to {} {}, which is not {} {}",
                         resolved.article(),
@@ -940,7 +1196,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                 // Disallow e.g. linking to enums with `struct@`
                 if let Res::Def(kind, _) = res {
                     debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator);
-                    match (self.kind_side_channel.take().unwrap_or(kind), disambiguator) {
+                    match (self.kind_side_channel.take().map(|(kind, _)| kind).unwrap_or(kind), disambiguator) {
                         | (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const)))
                         // NOTE: this allows 'method' to mean both normal functions and associated functions
                         // This can't cause ambiguity because both are in the same namespace.
@@ -1074,21 +1330,16 @@ impl Disambiguator {
         }
     }
 
-    /// Return (description of the change, suggestion)
-    fn suggestion_for(self, path_str: &str) -> (&'static str, String) {
-        const PREFIX: &str = "prefix with the item kind";
-        const FUNCTION: &str = "add parentheses";
-        const MACRO: &str = "add an exclamation mark";
-
+    fn suggestion(self) -> Suggestion {
         let kind = match self {
-            Disambiguator::Primitive => return (PREFIX, format!("prim@{}", path_str)),
+            Disambiguator::Primitive => return Suggestion::Prefix("prim"),
             Disambiguator::Kind(kind) => kind,
             Disambiguator::Namespace(_) => panic!("display_for cannot be used on namespaces"),
         };
         if kind == DefKind::Macro(MacroKind::Bang) {
-            return (MACRO, format!("{}!", path_str));
+            return Suggestion::Macro;
         } else if kind == DefKind::Fn || kind == DefKind::AssocFn {
-            return (FUNCTION, format!("{}()", path_str));
+            return Suggestion::Function;
         }
 
         let prefix = match kind {
@@ -1113,8 +1364,7 @@ impl Disambiguator {
             },
         };
 
-        // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
-        (PREFIX, format!("{}@{}", prefix, path_str))
+        Suggestion::Prefix(prefix)
     }
 
     fn ns(self) -> Namespace {
@@ -1146,6 +1396,31 @@ impl Disambiguator {
     }
 }
 
+enum Suggestion {
+    Prefix(&'static str),
+    Function,
+    Macro,
+}
+
+impl Suggestion {
+    fn descr(&self) -> Cow<'static, str> {
+        match self {
+            Self::Prefix(x) => format!("prefix with `{}@`", x).into(),
+            Self::Function => "add parentheses".into(),
+            Self::Macro => "add an exclamation mark".into(),
+        }
+    }
+
+    fn as_help(&self, path_str: &str) -> String {
+        // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
+        match self {
+            Self::Prefix(prefix) => format!("{}@{}", prefix, path_str),
+            Self::Function => format!("{}()", path_str),
+            Self::Macro => format!("{}!", path_str),
+        }
+    }
+}
+
 /// Reports a diagnostic for an intra-doc link.
 ///
 /// If no link range is provided, or the source span of the link cannot be determined, the span of
@@ -1161,7 +1436,7 @@ fn report_diagnostic(
     msg: &str,
     item: &Item,
     dox: &str,
-    link_range: Option<Range<usize>>,
+    link_range: &Option<Range<usize>>,
     decorate: impl FnOnce(&mut DiagnosticBuilder<'_>, Option<rustc_span::Span>),
 ) {
     let hir_id = match cx.as_local_hir_id(item.def_id) {
@@ -1213,24 +1488,197 @@ fn report_diagnostic(
 }
 
 fn resolution_failure(
-    cx: &DocContext<'_>,
+    collector: &LinkCollector<'_, '_>,
     item: &Item,
     path_str: &str,
+    disambiguator: Option<Disambiguator>,
     dox: &str,
     link_range: Option<Range<usize>>,
+    kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
 ) {
     report_diagnostic(
-        cx,
+        collector.cx,
         &format!("unresolved link to `{}`", path_str),
         item,
         dox,
-        link_range,
+        &link_range,
         |diag, sp| {
-            if let Some(sp) = sp {
-                diag.span_label(sp, "unresolved link");
-            }
+            let in_scope = kinds.iter().any(|kind| kind.res().is_some());
+            let item = |res: Res| {
+                format!(
+                    "the {} `{}`",
+                    res.descr(),
+                    collector.cx.tcx.item_name(res.def_id()).to_string()
+                )
+            };
+            let assoc_item_not_allowed = |res: Res| {
+                let def_id = res.def_id();
+                let name = collector.cx.tcx.item_name(def_id);
+                format!(
+                    "`{}` is {} {}, not a module or type, and cannot have associated items",
+                    name,
+                    res.article(),
+                    res.descr()
+                )
+            };
+            // ignore duplicates
+            let mut variants_seen = SmallVec::<[_; 3]>::new();
+            for mut failure in kinds {
+                // Check if _any_ parent of the path gets resolved.
+                // If so, report it and say the first which failed; if not, say the first path segment didn't resolve.
+                if let ResolutionFailure::NotInScope { module_id, name } = &mut failure {
+                    let mut current = name.as_ref();
+                    loop {
+                        current = match current.rsplitn(2, "::").nth(1) {
+                            Some(p) => p,
+                            None => {
+                                *name = current.to_owned().into();
+                                break;
+                            }
+                        };
+                        if let Some(res) = collector.check_full_res(
+                            TypeNS,
+                            &current,
+                            Some(*module_id),
+                            &None,
+                            &None,
+                        ) {
+                            failure = ResolutionFailure::NoAssocItem(res, Symbol::intern(current));
+                            break;
+                        }
+                    }
+                }
+                let variant = std::mem::discriminant(&failure);
+                if variants_seen.contains(&variant) {
+                    continue;
+                }
+                variants_seen.push(variant);
+                let note = match failure {
+                    ResolutionFailure::NotInScope { module_id, name, .. } => {
+                        if in_scope {
+                            continue;
+                        }
+                        // NOTE: uses an explicit `continue` so the `note:` will come before the `help:`
+                        let module_name = collector.cx.tcx.item_name(module_id);
+                        let note = format!("no item named `{}` in `{}`", name, module_name);
+                        if let Some(span) = sp {
+                            diag.span_label(span, &note);
+                        } else {
+                            diag.note(&note);
+                        }
+                        // If the link has `::` in the path, assume it's meant to be an intra-doc link
+                        if !path_str.contains("::") {
+                            // Otherwise, the `[]` might be unrelated.
+                            // FIXME(https://github.com/raphlinus/pulldown-cmark/issues/373):
+                            // don't show this for autolinks (`<>`), `()` style links, or reference links
+                            diag.help(r#"to escape `[` and `]` characters, add '\' before them like `\[` or `\]`"#);
+                        }
+                        continue;
+                    }
+                    ResolutionFailure::Dummy => continue,
+                    ResolutionFailure::WrongNamespace(res, expected_ns) => {
+                        if let Res::Def(kind, _) = res {
+                            let disambiguator = Disambiguator::Kind(kind);
+                            suggest_disambiguator(
+                                disambiguator,
+                                diag,
+                                path_str,
+                                dox,
+                                sp,
+                                &link_range,
+                            )
+                        }
 
-            diag.help(r#"to escape `[` and `]` characters, add '\' before them like `\[` or `\]`"#);
+                        format!(
+                            "this link resolves to {}, which is not in the {} namespace",
+                            item(res),
+                            expected_ns.descr()
+                        )
+                    }
+                    ResolutionFailure::NoParentItem => {
+                        diag.level = rustc_errors::Level::Bug;
+                        "all intra doc links should have a parent item".to_owned()
+                    }
+                    ResolutionFailure::NoPrimitiveImpl(res, _) => format!(
+                        "this link partially resolves to {}, which does not have any associated items",
+                        item(res),
+                    ),
+                    ResolutionFailure::NoPrimitiveAssocItem { prim_name, assoc_item, .. } => {
+                        format!(
+                            "the builtin type `{}` does not have an associated item named `{}`",
+                            prim_name, assoc_item
+                        )
+                    }
+                    ResolutionFailure::NoAssocItem(res, assoc_item) => {
+                        use DefKind::*;
+
+                        let (kind, def_id) = match res {
+                            Res::Def(kind, def_id) => (kind, def_id),
+                            x => unreachable!(
+                                "primitives are covered above and other `Res` variants aren't possible at module scope: {:?}",
+                                x,
+                            ),
+                        };
+                        let name = collector.cx.tcx.item_name(def_id);
+                        let path_description = if let Some(disambiguator) = disambiguator {
+                            disambiguator.descr()
+                        } else {
+                            match kind {
+                                Mod | ForeignMod => "inner item",
+                                Struct => "field or associated item",
+                                Enum | Union => "variant or associated item",
+                                Variant
+                                | Field
+                                | Closure
+                                | Generator
+                                | AssocTy
+                                | AssocConst
+                                | AssocFn
+                                | Fn
+                                | Macro(_)
+                                | Const
+                                | ConstParam
+                                | ExternCrate
+                                | Use
+                                | LifetimeParam
+                                | Ctor(_, _)
+                                | AnonConst => {
+                                    let note = assoc_item_not_allowed(res);
+                                    if let Some(span) = sp {
+                                        diag.span_label(span, &note);
+                                    } else {
+                                        diag.note(&note);
+                                    }
+                                    return;
+                                }
+                                Trait | TyAlias | ForeignTy | OpaqueTy | TraitAlias | TyParam
+                                | Static => "associated item",
+                                Impl | GlobalAsm => unreachable!("not a path"),
+                            }
+                        };
+                        format!(
+                            "the {} `{}` has no {} named `{}`",
+                            res.descr(),
+                            name,
+                            path_description,
+                            assoc_item
+                        )
+                    }
+                    ResolutionFailure::CannotHaveAssociatedItems(res, _) => {
+                        assoc_item_not_allowed(res)
+                    }
+                    ResolutionFailure::NotAVariant(res, variant) => format!(
+                        "this link partially resolves to {}, but there is no variant named {}",
+                        item(res),
+                        variant
+                    ),
+                };
+                if let Some(span) = sp {
+                    diag.span_label(span, &note);
+                } else {
+                    diag.note(&note);
+                }
+            }
         },
     );
 }
@@ -1245,31 +1693,14 @@ fn anchor_failure(
 ) {
     let msg = match failure {
         AnchorFailure::MultipleAnchors => format!("`{}` contains multiple anchors", path_str),
-        AnchorFailure::Primitive
-        | AnchorFailure::Variant
-        | AnchorFailure::AssocConstant
-        | AnchorFailure::AssocType
-        | AnchorFailure::Field
-        | AnchorFailure::Method => {
-            let kind = match failure {
-                AnchorFailure::Primitive => "primitive type",
-                AnchorFailure::Variant => "enum variant",
-                AnchorFailure::AssocConstant => "associated constant",
-                AnchorFailure::AssocType => "associated type",
-                AnchorFailure::Field => "struct field",
-                AnchorFailure::Method => "method",
-                AnchorFailure::MultipleAnchors => unreachable!("should be handled already"),
-            };
-
-            format!(
-                "`{}` contains an anchor, but links to {kind}s are already anchored",
-                path_str,
-                kind = kind
-            )
-        }
+        AnchorFailure::RustdocAnchorConflict(res) => format!(
+            "`{}` contains an anchor, but links to {kind}s are already anchored",
+            path_str,
+            kind = res.descr(),
+        ),
     };
 
-    report_diagnostic(cx, &msg, item, dox, link_range, |diag, sp| {
+    report_diagnostic(cx, &msg, item, dox, &link_range, |diag, sp| {
         if let Some(sp) = sp {
             diag.span_label(sp, "contains invalid anchor");
         }
@@ -1308,7 +1739,7 @@ fn ambiguity_error(
         }
     }
 
-    report_diagnostic(cx, &msg, item, dox, link_range.clone(), |diag, sp| {
+    report_diagnostic(cx, &msg, item, dox, &link_range, |diag, sp| {
         if let Some(sp) = sp {
             diag.span_label(sp, "ambiguous link");
         } else {
@@ -1330,18 +1761,20 @@ fn suggest_disambiguator(
     sp: Option<rustc_span::Span>,
     link_range: &Option<Range<usize>>,
 ) {
-    let (action, mut suggestion) = disambiguator.suggestion_for(path_str);
-    let help = format!("to link to the {}, {}", disambiguator.descr(), action);
+    let suggestion = disambiguator.suggestion();
+    let help = format!("to link to the {}, {}", disambiguator.descr(), suggestion.descr());
 
     if let Some(sp) = sp {
         let link_range = link_range.as_ref().expect("must have a link range if we have a span");
-        if dox.bytes().nth(link_range.start) == Some(b'`') {
-            suggestion = format!("`{}`", suggestion);
-        }
+        let msg = if dox.bytes().nth(link_range.start) == Some(b'`') {
+            format!("`{}`", suggestion.as_help(path_str))
+        } else {
+            suggestion.as_help(path_str)
+        };
 
-        diag.span_suggestion(sp, &help, suggestion, Applicability::MaybeIncorrect);
+        diag.span_suggestion(sp, &help, msg, Applicability::MaybeIncorrect);
     } else {
-        diag.help(&format!("{}: {}", help, suggestion));
+        diag.help(&format!("{}: {}", help, suggestion.as_help(path_str)));
     }
 }
 
@@ -1356,7 +1789,7 @@ fn privacy_error(
     let msg =
         format!("public documentation for `{}` links to private item `{}`", item_name, path_str);
 
-    report_diagnostic(cx, &msg, item, dox, link_range, |diag, sp| {
+    report_diagnostic(cx, &msg, item, dox, &link_range, |diag, sp| {
         if let Some(sp) = sp {
             diag.span_label(sp, "this item is private");
         }
@@ -1375,16 +1808,16 @@ fn handle_variant(
     cx: &DocContext<'_>,
     res: Res,
     extra_fragment: &Option<String>,
-) -> Result<(Res, Option<String>), ErrorKind> {
+) -> Result<(Res, Option<String>), ErrorKind<'static>> {
     use rustc_middle::ty::DefIdTree;
 
     if extra_fragment.is_some() {
-        return Err(ErrorKind::AnchorFailure(AnchorFailure::Variant));
+        return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res)));
     }
     let parent = if let Some(parent) = cx.tcx.parent(res.def_id()) {
         parent
     } else {
-        return Err(ErrorKind::ResolutionFailure);
+        return Err(ResolutionFailure::NoParentItem.into());
     };
     let parent_def = Res::Def(DefKind::Enum, parent);
     let variant = cx.tcx.expect_variant_res(res);
diff --git a/src/test/rustdoc-ui/assoc-item-not-in-scope.stderr b/src/test/rustdoc-ui/assoc-item-not-in-scope.stderr
index 8827c9351a6..92d27179e8c 100644
--- a/src/test/rustdoc-ui/assoc-item-not-in-scope.stderr
+++ b/src/test/rustdoc-ui/assoc-item-not-in-scope.stderr
@@ -2,14 +2,13 @@ error: unresolved link to `S::fmt`
   --> $DIR/assoc-item-not-in-scope.rs:4:14
    |
 LL | /// Link to [`S::fmt`]
-   |              ^^^^^^^^ unresolved link
+   |              ^^^^^^^^ the struct `S` has no field or associated item named `fmt`
    |
 note: the lint level is defined here
   --> $DIR/assoc-item-not-in-scope.rs:1:9
    |
 LL | #![deny(broken_intra_doc_links)]
    |         ^^^^^^^^^^^^^^^^^^^^^^
-   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 error: aborting due to previous error
 
diff --git a/src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr b/src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr
index 7530e3ad0f5..5020b97b2f2 100644
--- a/src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr
+++ b/src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr
@@ -2,7 +2,7 @@ error: unresolved link to `v2`
   --> $DIR/deny-intra-link-resolution-failure.rs:3:6
    |
 LL | /// [v2]
-   |      ^^ unresolved link
+   |      ^^ no item named `v2` in `deny_intra_link_resolution_failure`
    |
 note: the lint level is defined here
   --> $DIR/deny-intra-link-resolution-failure.rs:1:9
diff --git a/src/test/rustdoc-ui/intra-doc-alias-ice.stderr b/src/test/rustdoc-ui/intra-doc-alias-ice.stderr
index f1c07e31cd7..771fc2204f5 100644
--- a/src/test/rustdoc-ui/intra-doc-alias-ice.stderr
+++ b/src/test/rustdoc-ui/intra-doc-alias-ice.stderr
@@ -2,14 +2,13 @@ error: unresolved link to `TypeAlias::hoge`
   --> $DIR/intra-doc-alias-ice.rs:5:30
    |
 LL | /// [broken cross-reference](TypeAlias::hoge)
-   |                              ^^^^^^^^^^^^^^^ unresolved link
+   |                              ^^^^^^^^^^^^^^^ the type alias `TypeAlias` has no associated item named `hoge`
    |
 note: the lint level is defined here
   --> $DIR/intra-doc-alias-ice.rs:1:9
    |
 LL | #![deny(broken_intra_doc_links)]
    |         ^^^^^^^^^^^^^^^^^^^^^^
-   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 error: aborting due to previous error
 
diff --git a/src/test/rustdoc-ui/intra-link-errors.rs b/src/test/rustdoc-ui/intra-link-errors.rs
new file mode 100644
index 00000000000..26b629b1313
--- /dev/null
+++ b/src/test/rustdoc-ui/intra-link-errors.rs
@@ -0,0 +1,88 @@
+#![deny(broken_intra_doc_links)]
+//~^ NOTE lint level is defined
+
+// FIXME: this should say that it was skipped (maybe an allowed by default lint?)
+/// [<invalid syntax>]
+
+/// [path::to::nonexistent::module]
+//~^ ERROR unresolved link
+//~| NOTE no item named `path` in `intra_link_errors`
+
+/// [path::to::nonexistent::macro!]
+//~^ ERROR unresolved link
+//~| NOTE no item named `path` in `intra_link_errors`
+
+/// [type@path::to::nonexistent::type]
+//~^ ERROR unresolved link
+//~| NOTE no item named `path` in `intra_link_errors`
+
+/// [std::io::not::here]
+//~^ ERROR unresolved link
+//~| NOTE the module `io` has no inner item
+
+/// [std::io::Error::x]
+//~^ ERROR unresolved link
+//~| NOTE the struct `Error` has no field
+
+/// [std::io::ErrorKind::x]
+//~^ ERROR unresolved link
+//~| NOTE the enum `ErrorKind` has no variant
+
+/// [f::A]
+//~^ ERROR unresolved link
+//~| NOTE `f` is a function, not a module
+
+/// [S::A]
+//~^ ERROR unresolved link
+//~| NOTE struct `S` has no field or associated item
+
+/// [S::fmt]
+//~^ ERROR unresolved link
+//~| NOTE struct `S` has no field or associated item
+
+/// [E::D]
+//~^ ERROR unresolved link
+//~| NOTE enum `E` has no variant or associated item
+
+/// [u8::not_found]
+//~^ ERROR unresolved link
+//~| NOTE the builtin type `u8` does not have an associated item named `not_found`
+
+/// [S!]
+//~^ ERROR unresolved link
+//~| HELP to link to the struct, prefix with `struct@`
+//~| NOTE this link resolves to the struct `S`
+pub fn f() {}
+#[derive(Debug)]
+pub struct S;
+
+pub enum E { A, B, C }
+
+/// [type@S::h]
+//~^ ERROR unresolved link
+//~| HELP to link to the associated function
+//~| NOTE not in the type namespace
+impl S {
+    pub fn h() {}
+}
+
+/// [type@T::g]
+//~^ ERROR unresolved link
+//~| HELP to link to the associated function
+//~| NOTE not in the type namespace
+
+/// [T::h!]
+//~^ ERROR unresolved link
+//~| NOTE `T` has no macro named `h`
+pub trait T {
+    fn g() {}
+}
+
+/// [m()]
+//~^ ERROR unresolved link
+//~| HELP to link to the macro
+//~| NOTE not in the value namespace
+#[macro_export]
+macro_rules! m {
+    () => {};
+}
diff --git a/src/test/rustdoc-ui/intra-link-errors.stderr b/src/test/rustdoc-ui/intra-link-errors.stderr
new file mode 100644
index 00000000000..fbf3dcbbec2
--- /dev/null
+++ b/src/test/rustdoc-ui/intra-link-errors.stderr
@@ -0,0 +1,116 @@
+error: unresolved link to `path::to::nonexistent::module`
+  --> $DIR/intra-link-errors.rs:7:6
+   |
+LL | /// [path::to::nonexistent::module]
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `path` in `intra_link_errors`
+   |
+note: the lint level is defined here
+  --> $DIR/intra-link-errors.rs:1:9
+   |
+LL | #![deny(broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^
+
+error: unresolved link to `path::to::nonexistent::macro`
+  --> $DIR/intra-link-errors.rs:11:6
+   |
+LL | /// [path::to::nonexistent::macro!]
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `path` in `intra_link_errors`
+
+error: unresolved link to `path::to::nonexistent::type`
+  --> $DIR/intra-link-errors.rs:15:6
+   |
+LL | /// [type@path::to::nonexistent::type]
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `path` in `intra_link_errors`
+
+error: unresolved link to `std::io::not::here`
+  --> $DIR/intra-link-errors.rs:19:6
+   |
+LL | /// [std::io::not::here]
+   |      ^^^^^^^^^^^^^^^^^^ the module `io` has no inner item named `not`
+
+error: unresolved link to `std::io::Error::x`
+  --> $DIR/intra-link-errors.rs:23:6
+   |
+LL | /// [std::io::Error::x]
+   |      ^^^^^^^^^^^^^^^^^ the struct `Error` has no field or associated item named `x`
+
+error: unresolved link to `std::io::ErrorKind::x`
+  --> $DIR/intra-link-errors.rs:27:6
+   |
+LL | /// [std::io::ErrorKind::x]
+   |      ^^^^^^^^^^^^^^^^^^^^^ the enum `ErrorKind` has no variant or associated item named `x`
+
+error: unresolved link to `f::A`
+  --> $DIR/intra-link-errors.rs:31:6
+   |
+LL | /// [f::A]
+   |      ^^^^ `f` is a function, not a module or type, and cannot have associated items
+
+error: unresolved link to `S::A`
+  --> $DIR/intra-link-errors.rs:35:6
+   |
+LL | /// [S::A]
+   |      ^^^^ the struct `S` has no field or associated item named `A`
+
+error: unresolved link to `S::fmt`
+  --> $DIR/intra-link-errors.rs:39:6
+   |
+LL | /// [S::fmt]
+   |      ^^^^^^ the struct `S` has no field or associated item named `fmt`
+
+error: unresolved link to `E::D`
+  --> $DIR/intra-link-errors.rs:43:6
+   |
+LL | /// [E::D]
+   |      ^^^^ the enum `E` has no variant or associated item named `D`
+
+error: unresolved link to `u8::not_found`
+  --> $DIR/intra-link-errors.rs:47:6
+   |
+LL | /// [u8::not_found]
+   |      ^^^^^^^^^^^^^ the builtin type `u8` does not have an associated item named `not_found`
+
+error: unresolved link to `S`
+  --> $DIR/intra-link-errors.rs:51:6
+   |
+LL | /// [S!]
+   |      ^^
+   |      |
+   |      this link resolves to the struct `S`, which is not in the macro namespace
+   |      help: to link to the struct, prefix with `struct@`: `struct@S`
+
+error: unresolved link to `T::g`
+  --> $DIR/intra-link-errors.rs:69:6
+   |
+LL | /// [type@T::g]
+   |      ^^^^^^^^^
+   |      |
+   |      this link resolves to the associated function `g`, which is not in the type namespace
+   |      help: to link to the associated function, add parentheses: `T::g()`
+
+error: unresolved link to `T::h`
+  --> $DIR/intra-link-errors.rs:74:6
+   |
+LL | /// [T::h!]
+   |      ^^^^^ the trait `T` has no macro named `h`
+
+error: unresolved link to `S::h`
+  --> $DIR/intra-link-errors.rs:61:6
+   |
+LL | /// [type@S::h]
+   |      ^^^^^^^^^
+   |      |
+   |      this link resolves to the associated function `h`, which is not in the type namespace
+   |      help: to link to the associated function, add parentheses: `S::h()`
+
+error: unresolved link to `m`
+  --> $DIR/intra-link-errors.rs:81:6
+   |
+LL | /// [m()]
+   |      ^^^
+   |      |
+   |      this link resolves to the macro `m`, which is not in the value namespace
+   |      help: to link to the macro, add an exclamation mark: `m!`
+
+error: aborting due to 16 previous errors
+
diff --git a/src/test/rustdoc-ui/intra-link-prim-conflict.rs b/src/test/rustdoc-ui/intra-link-prim-conflict.rs
index 548d3e2544a..85738ceae8e 100644
--- a/src/test/rustdoc-ui/intra-link-prim-conflict.rs
+++ b/src/test/rustdoc-ui/intra-link-prim-conflict.rs
@@ -18,13 +18,13 @@
 
 /// [struct@char]
 //~^ ERROR incompatible link
-//~| HELP prefix with the item kind
+//~| HELP prefix with `mod@`
 //~| NOTE resolved to a module
 pub mod char {}
 
 pub mod inner {
     //! [struct@char]
     //~^ ERROR incompatible link
-    //~| HELP prefix with the item kind
+    //~| HELP prefix with `prim@`
     //~| NOTE resolved to a builtin type
 }
diff --git a/src/test/rustdoc-ui/intra-link-prim-conflict.stderr b/src/test/rustdoc-ui/intra-link-prim-conflict.stderr
index 53dccfbf1a2..43587a80021 100644
--- a/src/test/rustdoc-ui/intra-link-prim-conflict.stderr
+++ b/src/test/rustdoc-ui/intra-link-prim-conflict.stderr
@@ -9,11 +9,11 @@ note: the lint level is defined here
    |
 LL | #![deny(broken_intra_doc_links)]
    |         ^^^^^^^^^^^^^^^^^^^^^^
-help: to link to the module, prefix with the item kind
+help: to link to the module, prefix with `mod@`
    |
 LL | /// [mod@char]
    |      ^^^^^^^^
-help: to link to the builtin type, prefix with the item kind
+help: to link to the builtin type, prefix with `prim@`
    |
 LL | /// [prim@char]
    |      ^^^^^^^^^
@@ -24,11 +24,11 @@ error: `char` is both a module and a builtin type
 LL | /// [type@char]
    |      ^^^^^^^^^ ambiguous link
    |
-help: to link to the module, prefix with the item kind
+help: to link to the module, prefix with `mod@`
    |
 LL | /// [mod@char]
    |      ^^^^^^^^
-help: to link to the builtin type, prefix with the item kind
+help: to link to the builtin type, prefix with `prim@`
    |
 LL | /// [prim@char]
    |      ^^^^^^^^^
@@ -37,25 +37,17 @@ error: incompatible link kind for `char`
   --> $DIR/intra-link-prim-conflict.rs:19:6
    |
 LL | /// [struct@char]
-   |      ^^^^^^^^^^^
+   |      ^^^^^^^^^^^ help: to link to the module, prefix with `mod@`: `mod@char`
    |
    = note: this link resolved to a module, which is not a struct
-help: to link to the module, prefix with the item kind
-   |
-LL | /// [mod@char]
-   |      ^^^^^^^^
 
 error: incompatible link kind for `char`
   --> $DIR/intra-link-prim-conflict.rs:26:10
    |
 LL |     //! [struct@char]
-   |          ^^^^^^^^^^^
+   |          ^^^^^^^^^^^ help: to link to the builtin type, prefix with `prim@`: `prim@char`
    |
    = note: this link resolved to a builtin type, which is not a struct
-help: to link to the builtin type, prefix with the item kind
-   |
-LL |     //! [prim@char]
-   |          ^^^^^^^^^
 
 error: aborting due to 4 previous errors
 
diff --git a/src/test/rustdoc-ui/intra-link-span-ice-55723.stderr b/src/test/rustdoc-ui/intra-link-span-ice-55723.stderr
index 6b0ff8f1162..3c13df20588 100644
--- a/src/test/rustdoc-ui/intra-link-span-ice-55723.stderr
+++ b/src/test/rustdoc-ui/intra-link-span-ice-55723.stderr
@@ -2,7 +2,7 @@ error: unresolved link to `i`
   --> $DIR/intra-link-span-ice-55723.rs:9:10
    |
 LL | /// (arr[i])
-   |           ^ unresolved link
+   |           ^ no item named `i` in `intra_link_span_ice_55723`
    |
 note: the lint level is defined here
   --> $DIR/intra-link-span-ice-55723.rs:1:9
diff --git a/src/test/rustdoc-ui/intra-links-ambiguity.stderr b/src/test/rustdoc-ui/intra-links-ambiguity.stderr
index 7912c046f1c..17891ca05ef 100644
--- a/src/test/rustdoc-ui/intra-links-ambiguity.stderr
+++ b/src/test/rustdoc-ui/intra-links-ambiguity.stderr
@@ -9,7 +9,7 @@ note: the lint level is defined here
    |
 LL | #![deny(broken_intra_doc_links)]
    |         ^^^^^^^^^^^^^^^^^^^^^^
-help: to link to the struct, prefix with the item kind
+help: to link to the struct, prefix with `struct@`
    |
 LL | /// [`struct@ambiguous`] is ambiguous.
    |      ^^^^^^^^^^^^^^^^^^
@@ -24,7 +24,7 @@ error: `ambiguous` is both a struct and a function
 LL | /// [ambiguous] is ambiguous.
    |      ^^^^^^^^^ ambiguous link
    |
-help: to link to the struct, prefix with the item kind
+help: to link to the struct, prefix with `struct@`
    |
 LL | /// [struct@ambiguous] is ambiguous.
    |      ^^^^^^^^^^^^^^^^
@@ -39,7 +39,7 @@ error: `multi_conflict` is a struct, a function, and a macro
 LL | /// [`multi_conflict`] is a three-way conflict.
    |      ^^^^^^^^^^^^^^^^ ambiguous link
    |
-help: to link to the struct, prefix with the item kind
+help: to link to the struct, prefix with `struct@`
    |
 LL | /// [`struct@multi_conflict`] is a three-way conflict.
    |      ^^^^^^^^^^^^^^^^^^^^^^^
@@ -58,11 +58,11 @@ error: `type_and_value` is both a module and a constant
 LL | /// Ambiguous [type_and_value].
    |                ^^^^^^^^^^^^^^ ambiguous link
    |
-help: to link to the module, prefix with the item kind
+help: to link to the module, prefix with `mod@`
    |
 LL | /// Ambiguous [mod@type_and_value].
    |                ^^^^^^^^^^^^^^^^^^
-help: to link to the constant, prefix with the item kind
+help: to link to the constant, prefix with `const@`
    |
 LL | /// Ambiguous [const@type_and_value].
    |                ^^^^^^^^^^^^^^^^^^^^
@@ -73,7 +73,7 @@ error: `foo::bar` is both an enum and a function
 LL | /// Ambiguous non-implied shortcut link [`foo::bar`].
    |                                          ^^^^^^^^^^ ambiguous link
    |
-help: to link to the enum, prefix with the item kind
+help: to link to the enum, prefix with `enum@`
    |
 LL | /// Ambiguous non-implied shortcut link [`enum@foo::bar`].
    |                                          ^^^^^^^^^^^^^^^
diff --git a/src/test/rustdoc-ui/intra-links-anchors.stderr b/src/test/rustdoc-ui/intra-links-anchors.stderr
index e737b84320d..1825a4ad1fa 100644
--- a/src/test/rustdoc-ui/intra-links-anchors.stderr
+++ b/src/test/rustdoc-ui/intra-links-anchors.stderr
@@ -1,4 +1,4 @@
-error: `Foo::f#hola` contains an anchor, but links to struct fields are already anchored
+error: `Foo::f#hola` contains an anchor, but links to fields are already anchored
   --> $DIR/intra-links-anchors.rs:25:15
    |
 LL | /// Or maybe [Foo::f#hola].
@@ -16,13 +16,13 @@ error: `hello#people#!` contains multiple anchors
 LL | /// Another anchor error: [hello#people#!].
    |                            ^^^^^^^^^^^^^^ contains invalid anchor
 
-error: `Enum::A#whatever` contains an anchor, but links to enum variants are already anchored
+error: `Enum::A#whatever` contains an anchor, but links to variants are already anchored
   --> $DIR/intra-links-anchors.rs:37:28
    |
 LL | /// Damn enum's variants: [Enum::A#whatever].
    |                            ^^^^^^^^^^^^^^^^ contains invalid anchor
 
-error: `u32#hello` contains an anchor, but links to primitive types are already anchored
+error: `u32#hello` contains an anchor, but links to builtin types are already anchored
   --> $DIR/intra-links-anchors.rs:43:6
    |
 LL | /// [u32#hello]
diff --git a/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.rs b/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.rs
index 54e507adfe5..b9c8e033b1b 100644
--- a/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.rs
+++ b/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.rs
@@ -14,27 +14,27 @@ trait T {}
 /// Link to [struct@S]
 //~^ ERROR incompatible link kind for `S`
 //~| NOTE this link resolved
-//~| HELP prefix with the item kind
+//~| HELP prefix with `enum@`
 
 /// Link to [mod@S]
 //~^ ERROR incompatible link kind for `S`
 //~| NOTE this link resolved
-//~| HELP prefix with the item kind
+//~| HELP prefix with `enum@`
 
 /// Link to [union@S]
 //~^ ERROR incompatible link kind for `S`
 //~| NOTE this link resolved
-//~| HELP prefix with the item kind
+//~| HELP prefix with `enum@`
 
 /// Link to [trait@S]
 //~^ ERROR incompatible link kind for `S`
 //~| NOTE this link resolved
-//~| HELP prefix with the item kind
+//~| HELP prefix with `enum@`
 
 /// Link to [struct@T]
 //~^ ERROR incompatible link kind for `T`
 //~| NOTE this link resolved
-//~| HELP prefix with the item kind
+//~| HELP prefix with `trait@`
 
 /// Link to [derive@m]
 //~^ ERROR incompatible link kind for `m`
@@ -44,22 +44,22 @@ trait T {}
 /// Link to [const@s]
 //~^ ERROR incompatible link kind for `s`
 //~| NOTE this link resolved
-//~| HELP prefix with the item kind
+//~| HELP prefix with `static@`
 
 /// Link to [static@c]
 //~^ ERROR incompatible link kind for `c`
 //~| NOTE this link resolved
-//~| HELP prefix with the item kind
+//~| HELP prefix with `const@`
 
 /// Link to [fn@c]
 //~^ ERROR incompatible link kind for `c`
 //~| NOTE this link resolved
-//~| HELP prefix with the item kind
+//~| HELP prefix with `const@`
 
 /// Link to [c()]
 //~^ ERROR incompatible link kind for `c`
 //~| NOTE this link resolved
-//~| HELP prefix with the item kind
+//~| HELP prefix with `const@`
 
 /// Link to [const@f]
 //~^ ERROR incompatible link kind for `f`
diff --git a/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.stderr b/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.stderr
index 27b94af0378..2e732baf6e0 100644
--- a/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.stderr
+++ b/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.stderr
@@ -2,7 +2,7 @@ error: incompatible link kind for `S`
   --> $DIR/intra-links-disambiguator-mismatch.rs:14:14
    |
 LL | /// Link to [struct@S]
-   |              ^^^^^^^^
+   |              ^^^^^^^^ help: to link to the enum, prefix with `enum@`: `enum@S`
    |
 note: the lint level is defined here
   --> $DIR/intra-links-disambiguator-mismatch.rs:1:9
@@ -10,58 +10,38 @@ note: the lint level is defined here
 LL | #![deny(broken_intra_doc_links)]
    |         ^^^^^^^^^^^^^^^^^^^^^^
    = note: this link resolved to an enum, which is not a struct
-help: to link to the enum, prefix with the item kind
-   |
-LL | /// Link to [enum@S]
-   |              ^^^^^^
 
 error: incompatible link kind for `S`
   --> $DIR/intra-links-disambiguator-mismatch.rs:19:14
    |
 LL | /// Link to [mod@S]
-   |              ^^^^^
+   |              ^^^^^ help: to link to the enum, prefix with `enum@`: `enum@S`
    |
    = note: this link resolved to an enum, which is not a module
-help: to link to the enum, prefix with the item kind
-   |
-LL | /// Link to [enum@S]
-   |              ^^^^^^
 
 error: incompatible link kind for `S`
   --> $DIR/intra-links-disambiguator-mismatch.rs:24:14
    |
 LL | /// Link to [union@S]
-   |              ^^^^^^^
+   |              ^^^^^^^ help: to link to the enum, prefix with `enum@`: `enum@S`
    |
    = note: this link resolved to an enum, which is not a union
-help: to link to the enum, prefix with the item kind
-   |
-LL | /// Link to [enum@S]
-   |              ^^^^^^
 
 error: incompatible link kind for `S`
   --> $DIR/intra-links-disambiguator-mismatch.rs:29:14
    |
 LL | /// Link to [trait@S]
-   |              ^^^^^^^
+   |              ^^^^^^^ help: to link to the enum, prefix with `enum@`: `enum@S`
    |
    = note: this link resolved to an enum, which is not a trait
-help: to link to the enum, prefix with the item kind
-   |
-LL | /// Link to [enum@S]
-   |              ^^^^^^
 
 error: incompatible link kind for `T`
   --> $DIR/intra-links-disambiguator-mismatch.rs:34:14
    |
 LL | /// Link to [struct@T]
-   |              ^^^^^^^^
+   |              ^^^^^^^^ help: to link to the trait, prefix with `trait@`: `trait@T`
    |
    = note: this link resolved to a trait, which is not a struct
-help: to link to the trait, prefix with the item kind
-   |
-LL | /// Link to [trait@T]
-   |              ^^^^^^^
 
 error: incompatible link kind for `m`
   --> $DIR/intra-links-disambiguator-mismatch.rs:39:14
@@ -75,49 +55,33 @@ error: incompatible link kind for `s`
   --> $DIR/intra-links-disambiguator-mismatch.rs:44:14
    |
 LL | /// Link to [const@s]
-   |              ^^^^^^^
+   |              ^^^^^^^ help: to link to the static, prefix with `static@`: `static@s`
    |
    = note: this link resolved to a static, which is not a constant
-help: to link to the static, prefix with the item kind
-   |
-LL | /// Link to [static@s]
-   |              ^^^^^^^^
 
 error: incompatible link kind for `c`
   --> $DIR/intra-links-disambiguator-mismatch.rs:49:14
    |
 LL | /// Link to [static@c]
-   |              ^^^^^^^^
+   |              ^^^^^^^^ help: to link to the constant, prefix with `const@`: `const@c`
    |
    = note: this link resolved to a constant, which is not a static
-help: to link to the constant, prefix with the item kind
-   |
-LL | /// Link to [const@c]
-   |              ^^^^^^^
 
 error: incompatible link kind for `c`
   --> $DIR/intra-links-disambiguator-mismatch.rs:54:14
    |
 LL | /// Link to [fn@c]
-   |              ^^^^
+   |              ^^^^ help: to link to the constant, prefix with `const@`: `const@c`
    |
    = note: this link resolved to a constant, which is not a function
-help: to link to the constant, prefix with the item kind
-   |
-LL | /// Link to [const@c]
-   |              ^^^^^^^
 
 error: incompatible link kind for `c`
   --> $DIR/intra-links-disambiguator-mismatch.rs:59:14
    |
 LL | /// Link to [c()]
-   |              ^^^
+   |              ^^^ help: to link to the constant, prefix with `const@`: `const@c`
    |
    = note: this link resolved to a constant, which is not a function
-help: to link to the constant, prefix with the item kind
-   |
-LL | /// Link to [const@c]
-   |              ^^^^^^^
 
 error: incompatible link kind for `f`
   --> $DIR/intra-links-disambiguator-mismatch.rs:64:14
diff --git a/src/test/rustdoc-ui/intra-links-warning-crlf.stderr b/src/test/rustdoc-ui/intra-links-warning-crlf.stderr
index 1e3a26fadfa..351f8fafa64 100644
--- a/src/test/rustdoc-ui/intra-links-warning-crlf.stderr
+++ b/src/test/rustdoc-ui/intra-links-warning-crlf.stderr
@@ -2,7 +2,7 @@ warning: unresolved link to `error`
   --> $DIR/intra-links-warning-crlf.rs:7:6
    |
 LL | /// [error]
-   |      ^^^^^ unresolved link
+   |      ^^^^^ no item named `error` in `intra_links_warning_crlf`
    |
    = note: `#[warn(broken_intra_doc_links)]` on by default
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
@@ -11,7 +11,7 @@ warning: unresolved link to `error1`
   --> $DIR/intra-links-warning-crlf.rs:12:11
    |
 LL | /// docs [error1]
-   |           ^^^^^^ unresolved link
+   |           ^^^^^^ no item named `error1` in `intra_links_warning_crlf`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -19,7 +19,7 @@ warning: unresolved link to `error2`
   --> $DIR/intra-links-warning-crlf.rs:15:11
    |
 LL | /// docs [error2]
-   |           ^^^^^^ unresolved link
+   |           ^^^^^^ no item named `error2` in `intra_links_warning_crlf`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -27,7 +27,7 @@ warning: unresolved link to `error`
   --> $DIR/intra-links-warning-crlf.rs:23:20
    |
 LL |  * It also has an [error].
-   |                    ^^^^^ unresolved link
+   |                    ^^^^^ no item named `error` in `intra_links_warning_crlf`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
diff --git a/src/test/rustdoc-ui/intra-links-warning.stderr b/src/test/rustdoc-ui/intra-links-warning.stderr
index 53f2476295e..0832e00d35a 100644
--- a/src/test/rustdoc-ui/intra-links-warning.stderr
+++ b/src/test/rustdoc-ui/intra-links-warning.stderr
@@ -2,56 +2,45 @@ warning: unresolved link to `Foo::baz`
   --> $DIR/intra-links-warning.rs:3:23
    |
 LL |        //! Test with [Foo::baz], [Bar::foo], ...
-   |                       ^^^^^^^^ unresolved link
+   |                       ^^^^^^^^ the struct `Foo` has no field or associated item named `baz`
    |
    = note: `#[warn(broken_intra_doc_links)]` on by default
-   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 warning: unresolved link to `Bar::foo`
   --> $DIR/intra-links-warning.rs:3:35
    |
 LL |        //! Test with [Foo::baz], [Bar::foo], ...
-   |                                   ^^^^^^^^ unresolved link
-   |
-   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
+   |                                   ^^^^^^^^ no item named `Bar` in `intra_links_warning`
 
 warning: unresolved link to `Uniooon::X`
   --> $DIR/intra-links-warning.rs:6:13
    |
 LL |      //! , [Uniooon::X] and [Qux::Z].
-   |             ^^^^^^^^^^ unresolved link
-   |
-   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
+   |             ^^^^^^^^^^ no item named `Uniooon` in `intra_links_warning`
 
 warning: unresolved link to `Qux::Z`
   --> $DIR/intra-links-warning.rs:6:30
    |
 LL |      //! , [Uniooon::X] and [Qux::Z].
-   |                              ^^^^^^ unresolved link
-   |
-   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
+   |                              ^^^^^^ no item named `Qux` in `intra_links_warning`
 
 warning: unresolved link to `Uniooon::X`
   --> $DIR/intra-links-warning.rs:10:14
    |
 LL |       //! , [Uniooon::X] and [Qux::Z].
-   |              ^^^^^^^^^^ unresolved link
-   |
-   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
+   |              ^^^^^^^^^^ no item named `Uniooon` in `intra_links_warning`
 
 warning: unresolved link to `Qux::Z`
   --> $DIR/intra-links-warning.rs:10:31
    |
 LL |       //! , [Uniooon::X] and [Qux::Z].
-   |                               ^^^^^^ unresolved link
-   |
-   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
+   |                               ^^^^^^ no item named `Qux` in `intra_links_warning`
 
 warning: unresolved link to `Qux:Y`
   --> $DIR/intra-links-warning.rs:14:13
    |
 LL |        /// [Qux:Y]
-   |             ^^^^^ unresolved link
+   |             ^^^^^ no item named `Qux:Y` in `intra_links_warning`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -59,7 +48,7 @@ warning: unresolved link to `error`
   --> $DIR/intra-links-warning.rs:58:30
    |
 LL |  * time to introduce a link [error]*/
-   |                              ^^^^^ unresolved link
+   |                              ^^^^^ no item named `error` in `intra_links_warning`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -67,7 +56,7 @@ warning: unresolved link to `error`
   --> $DIR/intra-links-warning.rs:64:30
    |
 LL |  * time to introduce a link [error]
-   |                              ^^^^^ unresolved link
+   |                              ^^^^^ no item named `error` in `intra_links_warning`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -81,6 +70,7 @@ LL | #[doc = "single line [error]"]
            
            single line [error]
                         ^^^^^
+   = note: no item named `error` in `intra_links_warning`
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 warning: unresolved link to `error`
@@ -93,6 +83,7 @@ LL | #[doc = "single line with \"escaping\" [error]"]
            
            single line with "escaping" [error]
                                         ^^^^^
+   = note: no item named `error` in `intra_links_warning`
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 warning: unresolved link to `error`
@@ -107,13 +98,14 @@ LL | | /// [error]
            
            [error]
             ^^^^^
+   = note: no item named `error` in `intra_links_warning`
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 warning: unresolved link to `error1`
   --> $DIR/intra-links-warning.rs:80:11
    |
 LL | /// docs [error1]
-   |           ^^^^^^ unresolved link
+   |           ^^^^^^ no item named `error1` in `intra_links_warning`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -121,7 +113,7 @@ warning: unresolved link to `error2`
   --> $DIR/intra-links-warning.rs:82:11
    |
 LL | /// docs [error2]
-   |           ^^^^^^ unresolved link
+   |           ^^^^^^ no item named `error2` in `intra_links_warning`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -129,7 +121,7 @@ warning: unresolved link to `BarA`
   --> $DIR/intra-links-warning.rs:21:10
    |
 LL | /// bar [BarA] bar
-   |          ^^^^ unresolved link
+   |          ^^^^ no item named `BarA` in `intra_links_warning`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -137,7 +129,7 @@ warning: unresolved link to `BarB`
   --> $DIR/intra-links-warning.rs:27:9
    |
 LL |  * bar [BarB] bar
-   |         ^^^^ unresolved link
+   |         ^^^^ no item named `BarB` in `intra_links_warning`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -145,7 +137,7 @@ warning: unresolved link to `BarC`
   --> $DIR/intra-links-warning.rs:34:6
    |
 LL | bar [BarC] bar
-   |      ^^^^ unresolved link
+   |      ^^^^ no item named `BarC` in `intra_links_warning`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -159,6 +151,7 @@ LL | #[doc = "Foo\nbar [BarD] bar\nbaz"]
            
            bar [BarD] bar
                 ^^^^
+   = note: no item named `BarD` in `intra_links_warning`
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 warning: unresolved link to `BarF`
@@ -174,6 +167,7 @@ LL | f!("Foo\nbar [BarF] bar\nbaz");
            
            bar [BarF] bar
                 ^^^^
+   = note: no item named `BarF` in `intra_links_warning`
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
    = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
 
diff --git a/src/test/rustdoc-ui/lint-group.stderr b/src/test/rustdoc-ui/lint-group.stderr
index 04296d2e44a..550b79f6e89 100644
--- a/src/test/rustdoc-ui/lint-group.stderr
+++ b/src/test/rustdoc-ui/lint-group.stderr
@@ -32,7 +32,7 @@ error: unresolved link to `error`
   --> $DIR/lint-group.rs:9:29
    |
 LL | /// what up, let's make an [error]
-   |                             ^^^^^ unresolved link
+   |                             ^^^^^ no item named `error` in `lint_group`
    |
 note: the lint level is defined here
   --> $DIR/lint-group.rs:7:9