]> git.lizzy.rs Git - rust.git/blobdiff - src/librustdoc/passes/collect_intra_doc_links.rs
Pass on the DefId so rustdoc can name it in suggestions
[rust.git] / src / librustdoc / passes / collect_intra_doc_links.rs
index 65d116b9c670c8d396252673def660394c62b839..287f5fcf805efa7c6ca527f6792053d78019ece4 100644 (file)
@@ -17,8 +17,9 @@
 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,11 +47,63 @@ pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
     }
 }
 
-enum ErrorKind {
-    ResolutionFailure,
+enum ErrorKind<'a> {
+    Resolve(ResolutionFailure<'a>),
     AnchorFailure(AnchorFailure),
 }
 
+#[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),
+    /// `String` is the base name of the path (not necessarily the whole link)
+    NotInScope(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,
+    /// the root of this path resolved, but it was not an enum.
+    NotAnEnum(Res),
+    /// 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, _)
+            | NotAnEnum(res)
+            | NotAVariant(res, _)
+            | WrongNamespace(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,
@@ -68,7 +121,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 +131,22 @@ fn new(cx: &'a DocContext<'tcx>) -> Self {
 
     fn variant_field(
         &self,
-        path_str: &str,
+        path_str: &'path str,
         current_item: &Option<String>,
         module_id: DefId,
-    ) -> Result<(Res, Option<String>), ErrorKind> {
+    ) -> 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(ErrorKind::Resolve(ResolutionFailure::NotInScope(path_str.into())))?;
+        // TODO: this looks very wrong, why are we requiring 3 fields?
         let path = split
             .next()
             .map(|f| {
@@ -99,14 +157,19 @@ fn variant_field(
                 }
                 f.to_owned()
             })
-            .ok_or(ErrorKind::ResolutionFailure)?;
+            // TODO: is this right?
+            .ok_or(ErrorKind::Resolve(ResolutionFailure::NotInScope(
+                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_err(|_| {
+                ErrorKind::Resolve(ResolutionFailure::NotInScope(path.to_string().into()))
+            })?;
         if let Res::Err = ty_res {
-            return Err(ErrorKind::ResolutionFailure);
+            return Err(ErrorKind::Resolve(ResolutionFailure::NotInScope(path.to_string().into())));
         }
         let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
         match ty_res {
@@ -118,9 +181,11 @@ fn variant_field(
                     .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(ErrorKind::Resolve(ResolutionFailure::Dummy));
                 }
-                match cx.tcx.type_of(did).kind {
+                match cx.tcx.type_of(did).kind() {
                     ty::Adt(def, _) if def.is_enum() => {
                         if def.all_fields().any(|item| item.ident.name == variant_field_name) {
                             Ok((
@@ -131,18 +196,25 @@ fn variant_field(
                                 )),
                             ))
                         } else {
-                            Err(ErrorKind::ResolutionFailure)
+                            Err(ErrorKind::Resolve(ResolutionFailure::NotAVariant(
+                                ty_res,
+                                variant_field_name,
+                            )))
                         }
                     }
-                    _ => Err(ErrorKind::ResolutionFailure),
+                    _ => unreachable!(),
                 }
             }
-            _ => Err(ErrorKind::ResolutionFailure),
+            _ => Err(ErrorKind::Resolve(ResolutionFailure::NotAnEnum(ty_res))),
         }
     }
 
     /// 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,38 +226,40 @@ fn macro_resolve(&self, path_str: &str, parent_id: Option<DefId>) -> Option<Res>
                 false,
             ) {
                 if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind {
-                    return Some(res.map_id(|_| panic!("unexpected id")));
+                    return 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 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);
                 if let Ok((_, res)) =
                     resolver.resolve_str_path_error(DUMMY_SP, path_str, MacroNS, module_id)
                 {
                     // don't resolve builtins like `#[derive]`
                     if let Res::Def(..) = res {
                         let res = res.map_id(|_| panic!("unexpected node_id"));
-                        return Some(res);
+                        return Ok(res);
                     }
                 }
             } else {
                 debug!("attempting to resolve item without parent module: {}", path_str);
+                return Err(ResolutionFailure::NoParentItem);
             }
-            None
+            return Err(ResolutionFailure::NotInScope(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.
@@ -195,8 +269,8 @@ fn resolve(
             });
             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 {
@@ -225,7 +299,7 @@ fn resolve(
                 };
 
                 if value != (ns == ValueNS) {
-                    return Err(ErrorKind::ResolutionFailure);
+                    return Err(ErrorKind::Resolve(ResolutionFailure::WrongNamespace(res, ns)));
                 }
             } else if let Some((path, prim)) = is_primitive(path_str, ns) {
                 if extra_fragment.is_some() {
@@ -236,9 +310,9 @@ fn resolve(
 
             // 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" {
@@ -248,10 +322,17 @@ fn resolve(
                     }
                     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(ErrorKind::Resolve(ResolutionFailure::NotInScope(
+                    item_name.to_string().into(),
+                )))?;
+
+            if let Some((path, prim)) = is_primitive(&path_root, ns) {
+                let impls = primitive_impl(cx, &path).ok_or_else(|| {
+                    ErrorKind::Resolve(ResolutionFailure::NoPrimitiveImpl(prim, path_root.into()))
+                })?;
+                for &impl_ in impls {
                     let link = cx
                         .tcx
                         .associated_items(impl_)
@@ -261,28 +342,35 @@ fn resolve(
                             ns,
                             impl_,
                         )
-                        .and_then(|item| match item.kind {
-                            ty::AssocKind::Fn => Some("method"),
-                            _ => None,
+                        .map(|item| match item.kind {
+                            ty::AssocKind::Fn => "method",
+                            ty::AssocKind::Const => "associatedconstant",
+                            ty::AssocKind::Type => "associatedtype",
                         })
                         .map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name))));
                     if let Some(link) = link {
                         return Ok(link);
                     }
                 }
-                return Err(ErrorKind::ResolutionFailure);
+                return Err(ErrorKind::Resolve(ResolutionFailure::NoPrimitiveAssocItem {
+                    res: prim,
+                    prim_name: path,
+                    assoc_item: item_name,
+                }));
             }
 
             let (_, ty_res) = cx
                 .enter_resolver(|resolver| {
-                    resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
+                    resolver.resolve_str_path_error(DUMMY_SP, &path_root, TypeNS, module_id)
                 })
-                .map_err(|_| ErrorKind::ResolutionFailure)?;
+                .map_err(|_| {
+                    ErrorKind::Resolve(ResolutionFailure::NotInScope(path_root.clone().into()))
+                })?;
             if let Res::Err = ty_res {
                 return if ns == Namespace::ValueNS {
                     self.variant_field(path_str, current_item, module_id)
                 } else {
-                    Err(ErrorKind::ResolutionFailure)
+                    Err(ErrorKind::Resolve(ResolutionFailure::NotInScope(path_root.into())))
                 };
             }
             let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
@@ -293,7 +381,7 @@ fn resolve(
                 ) => {
                     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()
@@ -305,7 +393,7 @@ fn resolve(
                                 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`
@@ -321,7 +409,7 @@ fn resolve(
                             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",
@@ -337,11 +425,12 @@ fn resolve(
                             // 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 {
-                        match cx.tcx.type_of(did).kind {
+                        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() {
                                     def.all_fields().find(|item| item.ident.name == item_name)
@@ -378,7 +467,9 @@ fn resolve(
                         }
                     } else {
                         // We already know this isn't in ValueNS, so no need to check variant_field
-                        return Err(ErrorKind::ResolutionFailure);
+                        return Err(ErrorKind::Resolve(ResolutionFailure::NoAssocItem(
+                            ty_res, item_name,
+                        )));
                     }
                 }
                 Res::Def(DefKind::Trait, did) => cx
@@ -417,12 +508,13 @@ fn resolve(
                 if ns == Namespace::ValueNS {
                     self.variant_field(path_str, current_item, module_id)
                 } else {
-                    Err(ErrorKind::ResolutionFailure)
+                    Err(ErrorKind::Resolve(ResolutionFailure::NoAssocItem(ty_res, item_name)))
                 }
             })
         } else {
             debug!("attempting to resolve item without parent module: {}", path_str);
-            Err(ErrorKind::ResolutionFailure)
+            // TODO: maybe this should just be an ICE?
+            Err(ErrorKind::Resolve(ResolutionFailure::NoParentItem))
         }
     }
 }
@@ -433,7 +525,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);
@@ -461,7 +553,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);
@@ -483,7 +575,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"),
@@ -500,12 +592,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,17 +628,19 @@ fn traits_implemented_by(cx: &DocContext<'_>, type_: DefId, module: DefId) -> Fx
             let impl_type = trait_ref.self_ty();
             debug!(
                 "comparing type {} with kind {:?} against type {:?}",
-                impl_type, impl_type.kind, type_
+                impl_type,
+                impl_type.kind(),
+                type_
             );
             // Fast path: if this is a primitive simple `==` will work
             saw_impl = impl_type == ty
-                || match impl_type.kind {
+                || match impl_type.kind() {
                     // Check if these are the same def_id
                     ty::Adt(def, _) => {
                         debug!("adt def_id: {:?}", def.did);
                         def.did == type_
                     }
-                    ty::Foreign(def_id) => def_id == type_,
+                    ty::Foreign(def_id) => *def_id == type_,
                     _ => false,
                 };
         });
@@ -558,10 +652,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
     {
@@ -578,6 +672,9 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
         let parent_node = if item.is_fake() {
             // FIXME: is this correct?
             None
+        // If we're documenting the crate root itself, it has no parent. Use the root instead.
+        } else if item.def_id.is_top_level_module() {
+            Some(item.def_id)
         } else {
             let mut current = item.def_id;
             // The immediate parent might not always be a module.
@@ -589,6 +686,12 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
                     }
                     current = parent;
                 } else {
+                    debug!(
+                        "{:?} has no parent (kind={:?}, original was {:?})",
+                        current,
+                        self.cx.tcx.def_kind(current),
+                        item.def_id
+                    );
                     break None;
                 }
             }
@@ -693,11 +796,12 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
                     // This is an anchor to an element of the current page, nothing to do in here!
                     continue;
                 }
-                (parts[0].to_owned(), Some(parts[1].to_owned()))
+                (parts[0], Some(parts[1].to_owned()))
             } else {
-                (parts[0].to_owned(), None)
+                (parts[0], None)
             };
             let resolved_self;
+            let link_text;
             let mut path_str;
             let disambiguator;
             let (mut res, mut fragment) = {
@@ -714,6 +818,12 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
                     continue;
                 }
 
+                // We stripped `()` and `!` when parsing the disambiguator.
+                // Add them back to be displayed, but not prefix disambiguators.
+                link_text = disambiguator
+                    .map(|d| d.display_for(path_str))
+                    .unwrap_or_else(|| path_str.to_owned());
+
                 // In order to correctly resolve intra-doc-links we need to
                 // pick a base AST node to work from.  If the documentation for
                 // this module came from an inner comment (//!) then we anchor
@@ -739,13 +849,48 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
                     }
                 }
 
+                // used for reporting better errors
+                let check_full_res = |this: &mut Self, ns| {
+                    let res =
+                        match this.resolve(path_str, ns, &current_item, base_node, &extra_fragment)
+                        {
+                            Ok(res) => {
+                                debug!(
+                                    "check_full_res: saw res for {} in {:?} ns: {:?}",
+                                    path_str, ns, res.0
+                                );
+                                Some(res.0)
+                            }
+                            Err(ErrorKind::Resolve(kind)) => kind.full_res(),
+                            // TODO: add `Res` to AnchorFailure
+                            Err(ErrorKind::AnchorFailure(_)) => None,
+                        };
+                    this.kind_side_channel.take().map(|(kind, id)| Res::Def(kind, id)).or(res)
+                };
+
                 match disambiguator.map(Disambiguator::ns) {
                     Some(ns @ (ValueNS | TypeNS)) => {
                         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(mut kind)) => {
+                                // We only looked in one namespace. Try to give a better error if possible.
+                                // TODO: handle MacroNS too
+                                if kind.full_res().is_none() {
+                                    let other_ns = if ns == ValueNS { TypeNS } else { ValueNS };
+                                    if let Some(res) = check_full_res(self, other_ns) {
+                                        // recall that this stores the _expected_ namespace
+                                        kind = ResolutionFailure::WrongNamespace(res, ns);
+                                    }
+                                }
+                                resolution_failure(
+                                    cx,
+                                    &item,
+                                    path_str,
+                                    &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.
@@ -772,13 +917,13 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
                             ) {
                                 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(kind)) => Err(kind),
                             },
                             value_ns: match self.resolve(
                                 path_str,
@@ -787,48 +932,62 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
                                 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(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 mut candidates_iter =
+                            candidates.iter().filter_map(|res| res.as_ref().ok());
+                        let len = candidates_iter.clone().count();
+
+                        if len == 0 {
+                            drop(candidates_iter);
+                            resolution_failure(
+                                cx,
+                                &item,
+                                path_str,
+                                &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_iter.next().unwrap().clone()
                         } else if len == 2 && is_derive_trait_collision(&candidates) {
+                            drop(candidates_iter);
                             candidates.type_ns.unwrap()
                         } else {
+                            drop(candidates_iter);
                             if is_derive_trait_collision(&candidates) {
-                                candidates.macro_ns = None;
+                                candidates.macro_ns =
+                                    Err(ResolutionFailure::NotInScope(path_str.into()));
                             }
+                            // 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,
@@ -841,11 +1000,28 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
                         }
                     }
                     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.
+                                //if kind.res().is_none() {
+                                for &ns in &[TypeNS, ValueNS] {
+                                    if let Some(res) = check_full_res(self, ns) {
+                                        kind = ResolutionFailure::WrongNamespace(res, MacroNS);
+                                        break;
+                                    }
+                                }
+                                //}
+                                resolution_failure(
+                                    cx,
+                                    &item,
+                                    path_str,
+                                    &dox,
+                                    link_range,
+                                    smallvec![kind],
+                                );
+                                continue;
+                            }
                         }
                     }
                 }
@@ -887,7 +1063,7 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
             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(),
@@ -902,7 +1078,12 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
             if let Res::PrimTy(_) = res {
                 match disambiguator {
                     Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
-                        item.attrs.links.push((ori_link, None, fragment))
+                        item.attrs.links.push(ItemLink {
+                            link: ori_link,
+                            link_text: path_str.to_owned(),
+                            did: None,
+                            fragment,
+                        });
                     }
                     Some(other) => {
                         report_mismatch(other, Disambiguator::Primitive);
@@ -915,7 +1096,7 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
                 // 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.
@@ -953,7 +1134,12 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
                     }
                 }
                 let id = register_res(cx, res);
-                item.attrs.links.push((ori_link, Some(id), fragment));
+                item.attrs.links.push(ItemLink {
+                    link: ori_link,
+                    link_text,
+                    did: Some(id),
+                    fragment,
+                });
             }
         }
 
@@ -971,15 +1157,6 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
             self.fold_item_recur(item)
         }
     }
-
-    // FIXME: if we can resolve intra-doc links from other crates, we can use the stock
-    // `fold_crate`, but until then we should avoid scanning `krate.external_traits` since those
-    // will never resolve properly
-    fn fold_crate(&mut self, mut c: Crate) -> Crate {
-        c.module = c.module.take().and_then(|module| self.fold_item(module));
-
-        c
-    }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -990,6 +1167,18 @@ enum Disambiguator {
 }
 
 impl Disambiguator {
+    /// The text that should be displayed when the path is rendered as HTML.
+    ///
+    /// NOTE: `path` is not the original link given by the user, but a name suitable for passing to `resolve`.
+    fn display_for(&self, path: &str) -> String {
+        match self {
+            // FIXME: this will have different output if the user had `m!()` originally.
+            Self::Kind(DefKind::Macro(MacroKind::Bang)) => format!("{}!", path),
+            Self::Kind(DefKind::Fn) => format!("{}()", path),
+            _ => path.to_owned(),
+        }
+    }
+
     /// (disambiguator, path_str)
     fn from_str(link: &str) -> Result<(Self, &str), ()> {
         use Disambiguator::{Kind, Namespace as NS, Primitive};
@@ -1042,7 +1231,7 @@ fn from_res(res: Res) -> Self {
     }
 
     /// Return (description of the change, suggestion)
-    fn display_for(self, path_str: &str) -> (&'static str, String) {
+    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";
@@ -1128,7 +1317,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) {
@@ -1164,7 +1353,7 @@ fn report_diagnostic(
                 // Print the line containing the `link_range` and manually mark it with '^'s.
                 diag.note(&format!(
                     "the link appears in this line:\n\n{line}\n\
-                         {indicator: <before$}{indicator:^<found$}",
+                     {indicator: <before$}{indicator:^<found$}",
                     line = line,
                     indicator = "",
                     before = link_range.start - last_new_line_offset,
@@ -1185,19 +1374,107 @@ fn resolution_failure(
     path_str: &str,
     dox: &str,
     link_range: Option<Range<usize>>,
+    kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
 ) {
     report_diagnostic(
         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 mut reported_not_in_scope = false;
+            let item = |res: Res| {
+                if let Some(id) = res.opt_def_id() {
+                    (format!("the {} `{}`", res.descr(), cx.tcx.item_name(id).to_string()), ",")
+                } else {
+                    (format!("{} {}", res.article(), res.descr()), "")
+                }
+            };
+            for failure in kinds {
+                match failure {
+                    // already handled above
+                    ResolutionFailure::NotInScope(base) => {
+                        if in_scope || reported_not_in_scope {
+                            continue;
+                        }
+                        reported_not_in_scope = true;
+                        diag.note(&format!("no item named `{}` is in scope", base));
+                        diag.help(r#"to escape `[` and `]` characters, add '\' before them like `\[` or `\]`"#);
+                    }
+                    ResolutionFailure::Dummy => continue,
+                    ResolutionFailure::WrongNamespace(res, expected_ns) => {
+                        let (item, comma) = item(res);
+                        let note = format!(
+                            "this link resolves to {}{} which is not in the {} namespace",
+                            item,
+                            comma,
+                            expected_ns.descr()
+                        );
+                        diag.note(&note);
+
+                        if let Res::Def(kind, _) = res {
+                            let disambiguator = Disambiguator::Kind(kind);
+                            suggest_disambiguator(
+                                disambiguator,
+                                diag,
+                                path_str,
+                                dox,
+                                sp,
+                                &link_range,
+                            )
+                        }
+                    }
+                    ResolutionFailure::NoParentItem => {
+                        panic!("all intra doc links should have a parent item")
+                    }
+                    ResolutionFailure::NoPrimitiveImpl(res, _) => {
+                        let (item, comma) = item(res);
+                        let note = format!(
+                            "this link partially resolves to {}{} which does not have any associated items",
+                            item, comma,
+                        );
+                        diag.note(&note);
+                    }
+                    ResolutionFailure::NoPrimitiveAssocItem { prim_name, assoc_item, .. } => {
+                        let note = format!(
+                            "the builtin type `{}` does not have an associated item named `{}`",
+                            prim_name, assoc_item
+                        );
+                        diag.note(&note);
+                    }
+                    ResolutionFailure::NoAssocItem(res, assoc_item) => {
+                        let (item, _) = item(res);
+                        diag.note(&format!("this link partially resolves to {}", item));
+                        // FIXME: when are items neither a primitive nor a Def?
+                        if let Res::Def(_, def_id) = res {
+                            let name = cx.tcx.item_name(def_id);
+                            let note = format!(
+                                "`{}` has no field, variant, or associated item named `{}`",
+                                name, assoc_item
+                            );
+                            diag.note(&note);
+                        }
+                    }
+                    // TODO: is there ever a case where this happens?
+                    ResolutionFailure::NotAnEnum(res) => {
+                        let (item, comma) = item(res);
+                        let note =
+                            format!("this link resolves to {}{} which is not an enum", item, comma);
+                        diag.note(&note);
+                        diag.note("if this were an enum, it might have a variant which resolved");
+                    }
+                    ResolutionFailure::NotAVariant(res, variant) => {
+                        let note = format!(
+                            "this link partially resolves to {}, but there is no variant named {}",
+                            item(res).0,
+                            variant
+                        );
+                        diag.note(&note);
+                    }
+                }
             }
-
-            diag.help(r#"to escape `[` and `]` characters, add '\' before them like `\[` or `\]`"#);
         },
     );
 }
@@ -1236,17 +1513,17 @@ fn anchor_failure(
         }
     };
 
-    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");
         }
     });
 }
 
-fn ambiguity_error(
+fn ambiguity_error<'a>(
     cx: &DocContext<'_>,
     item: &Item,
-    path_str: &str,
+    path_str: &'a str,
     dox: &str,
     link_range: Option<Range<usize>>,
     candidates: Vec<Res>,
@@ -1275,7 +1552,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 {
@@ -1297,7 +1574,7 @@ fn suggest_disambiguator(
     sp: Option<rustc_span::Span>,
     link_range: &Option<Range<usize>>,
 ) {
-    let (action, mut suggestion) = disambiguator.display_for(path_str);
+    let (action, mut suggestion) = disambiguator.suggestion_for(path_str);
     let help = format!("to link to the {}, {}", disambiguator.descr(), action);
 
     if let Some(sp) = sp {
@@ -1323,7 +1600,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");
         }
@@ -1342,7 +1619,7 @@ 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() {
@@ -1351,7 +1628,8 @@ fn handle_variant(
     let parent = if let Some(parent) = cx.tcx.parent(res.def_id()) {
         parent
     } else {
-        return Err(ErrorKind::ResolutionFailure);
+        // TODO: this should just be an unwrap, there should never be `Variant`s without a parent
+        return Err(ErrorKind::Resolve(ResolutionFailure::NoParentItem));
     };
     let parent_def = Res::Def(DefKind::Enum, parent);
     let variant = cx.tcx.expect_variant_res(res);