about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJoshua Nelson <jyn514@gmail.com>2020-09-20 00:32:00 -0400
committerJoshua Nelson <jyn514@gmail.com>2020-09-23 21:04:20 -0400
commitdd7b8c85a6a2bffb2cce1c40ba72680a1d7be93b (patch)
tree2a23e7415df3f7537d4399bce754b155fc1388ac
parentcbc5e4d4d5715cfe111def11bbd1d8afae2ea80e (diff)
downloadrust-dd7b8c85a6a2bffb2cce1c40ba72680a1d7be93b.tar.gz
rust-dd7b8c85a6a2bffb2cce1c40ba72680a1d7be93b.zip
Perform most diagnostic lookups in `resolution_failure`
Previously, these were spread throughout the codebase. This had two
drawbacks:

1. It caused the fast path to be slower: even if a link resolved,
rustdoc would still perform various lookups for the error diagnostic.
2. It was inconsistent and didn't always give all diagnostics (https://github.com/rust-lang/rust/issues/76925)

Now, diagnostics only perform expensive lookups in the error case.
Additionally, the error handling is much more consistent, both in
wording and behavior.

- Remove `CannotHaveAssociatedItems`, `NotInScope`, `NoAssocItem`, and `NotAVariant`
  in favor of the more general `NotResolved`

  `resolution_failure` will now look up which of the four above
  categories is relevant, instead of requiring the rest of the code to
  be consistent and accurate in which it picked.

- Remove unnecessary lookups throughout the intra-doc link pass. These
are now done by `resolution_failure`.
  + Remove unnecessary `extra_fragment` argument to `variant_field()`;
    it was only used to do lookups on failure.
  + Remove various lookups related to associated items
  + Remove distinction between 'not in scope' and 'no associated item'

- Don't perform unnecessary copies
- Remove unused variables and code
- Update tests
- Note why looking at other namespaces is still necessary
- 'has no inner item' -> 'contains no item'

bless tests
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs397
-rw-r--r--src/test/rustdoc-ui/deny-intra-link-resolution-failure.stderr2
-rw-r--r--src/test/rustdoc-ui/intra-link-errors.rs16
-rw-r--r--src/test/rustdoc-ui/intra-link-errors.stderr51
-rw-r--r--src/test/rustdoc-ui/intra-link-span-ice-55723.stderr2
-rw-r--r--src/test/rustdoc-ui/intra-links-warning-crlf.stderr8
-rw-r--r--src/test/rustdoc-ui/intra-links-warning.stderr36
-rw-r--r--src/test/rustdoc-ui/lint-group.stderr2
-rw-r--r--src/test/rustdoc/intra-link-associated-items.rs2
9 files changed, 263 insertions, 253 deletions
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 5a9eeec4dfe..71fd5deca54 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -60,14 +60,6 @@ impl<'a> From<ResolutionFailure<'a>> for ErrorKind<'a> {
 
 #[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
@@ -75,33 +67,19 @@ enum ResolutionFailure<'a> {
     /// `[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),
+    /// This resolved, but with the wrong namespace.
+    /// `Namespace` is the expected namespace (as opposed to the actual).
+    WrongNamespace(Res, Namespace),
+    /// The link failed to resolve. `resolution_failure` should look to see if there's
+    /// a more helpful error that can be given.
+    NotResolved { module_id: DefId, partial_res: Option<Res>, unresolved: Cow<'a, str> },
     /// 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 {
@@ -136,22 +114,25 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         path_str: &'path str,
         current_item: &Option<String>,
         module_id: DefId,
-        extra_fragment: &Option<String>,
     ) -> Result<(Res, Option<String>), ErrorKind<'path>> {
         let cx = self.cx;
+        let no_res = || ResolutionFailure::NotResolved {
+            module_id,
+            partial_res: None,
+            unresolved: path_str.into(),
+        };
 
         debug!("looking for enum variant {}", path_str);
         let mut split = path_str.rsplitn(3, "::");
-        let variant_field_name = split
+        let (variant_field_str, variant_field_name) = split
             .next()
-            .map(|f| Symbol::intern(f))
+            .map(|f| (f, Symbol::intern(f)))
             .expect("fold_item should ensure link is non-empty");
-        let variant_name =
+        let (variant_str, variant_name) =
             // 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(),
-            })?;
+            // If there's no second component, the link looks like `[path]`.
+            // So there's no partial res and we should say the whole link failed to resolve.
+            split.next().map(|f| (f, Symbol::intern(f))).ok_or_else(no_res)?;
         let path = split
             .next()
             .map(|f| {
@@ -162,10 +143,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 }
                 f.to_owned()
             })
-            .ok_or_else(|| ResolutionFailure::NotInScope {
-                module_id,
-                name: variant_name.to_string().into(),
-            })?;
+            // If there's no third component, we saw `[a::b]` before and it failed to resolve.
+            // So there's no partial res.
+            .ok_or_else(no_res)?;
         let ty_res = cx
             .enter_resolver(|resolver| {
                 resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
@@ -173,7 +153,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
             .map(|(_, res)| res)
             .unwrap_or(Res::Err);
         if let Res::Err = ty_res {
-            return Err(ResolutionFailure::NotInScope { module_id, name: path.into() }.into());
+            return Err(no_res().into());
         }
         let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
         match ty_res {
@@ -196,38 +176,27 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                                 ty_res,
                                 Some(format!(
                                     "variant.{}.field.{}",
-                                    variant_name, variant_field_name
+                                    variant_str, variant_field_name
                                 )),
                             ))
                         } else {
-                            Err(ResolutionFailure::NotAVariant(ty_res, variant_field_name).into())
+                            Err(ResolutionFailure::NotResolved {
+                                module_id,
+                                partial_res: Some(Res::Def(DefKind::Enum, def.did)),
+                                unresolved: variant_field_str.into(),
+                            }
+                            .into())
                         }
                     }
                     _ => unreachable!(),
                 }
             }
-            // `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,
-                    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())
+            _ => Err(ResolutionFailure::NotResolved {
+                module_id,
+                partial_res: Some(ty_res),
+                unresolved: variant_str.into(),
             }
+            .into()),
         }
     }
 
@@ -248,11 +217,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 false,
             ) {
                 if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind {
-                    return Some(Ok(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(Ok(res.map_id(|_| panic!("unexpected id"))));
+                return Ok(res.map_id(|_| panic!("unexpected id")));
             }
             debug!("resolving {} as a macro in the module {:?}", path_str, module_id);
             if let Ok((_, res)) =
@@ -261,28 +230,14 @@ 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(Ok(res));
-                }
-            }
-            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, module_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))
-                    });
+                    return Ok(res);
                 }
             }
-            Err(ResolutionFailure::NotInScope { module_id, name: path_str.into() })
+            Err(ResolutionFailure::NotResolved {
+                module_id,
+                partial_res: None,
+                unresolved: path_str.into(),
+            })
         })
     }
 
@@ -347,7 +302,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         // Try looking for methods and associated items.
         let mut split = path_str.rsplitn(2, "::");
         // this can be an `unwrap()` because we ensure the link is never empty
-        let item_name = Symbol::intern(split.next().unwrap());
+        let (item_str, item_name) = split.next().map(|i| (i, Symbol::intern(i))).unwrap();
         let path_root = split
             .next()
             .map(|f| {
@@ -362,7 +317,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
             // 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() }
+                ResolutionFailure::NotResolved {
+                    module_id,
+                    partial_res: None,
+                    unresolved: item_str.into(),
+                }
             })?;
 
         if let Some((path, prim)) = is_primitive(&path_root, TypeNS) {
@@ -383,7 +342,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         ty::AssocKind::Const => "associatedconstant",
                         ty::AssocKind::Type => "associatedtype",
                     })
-                    .map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name))));
+                    .map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_str))));
                 if let Some(link) = link {
                     return Ok(link);
                 }
@@ -411,25 +370,14 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         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)
+                    self.variant_field(path_str, current_item, module_id)
                 } 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())
+                    Err(ResolutionFailure::NotResolved {
+                        module_id,
+                        partial_res: None,
+                        unresolved: path_root.into(),
+                    }
+                    .into())
                 };
             }
             Ok(res) => res,
@@ -479,7 +427,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         // 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(), id)));
-                        Ok((ty_res, Some(format!("{}.{}", out, item_name))))
+                        Ok((ty_res, Some(format!("{}.{}", out, item_str))))
                     })
                 } else if ns == Namespace::ValueNS {
                     debug!("looking for variants or fields named {} for {:?}", item_name, did);
@@ -522,7 +470,12 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                     }
                 } else {
                     // We already know this isn't in ValueNS, so no need to check variant_field
-                    return Err(ResolutionFailure::NoAssocItem(ty_res, item_name).into());
+                    return Err(ResolutionFailure::NotResolved {
+                        module_id,
+                        partial_res: Some(ty_res),
+                        unresolved: item_str.into(),
+                    }
+                    .into());
                 }
             }
             Res::Def(DefKind::Trait, did) => cx
@@ -546,16 +499,21 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         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))))
+                        Ok((res, Some(format!("{}.{}", kind, item_str))))
                     }
                 }),
             _ => None,
         };
         res.unwrap_or_else(|| {
             if ns == Namespace::ValueNS {
-                self.variant_field(path_str, current_item, module_id, extra_fragment)
+                self.variant_field(path_str, current_item, module_id)
             } else {
-                Err(ResolutionFailure::NoAssocItem(ty_res, item_name).into())
+                Err(ResolutionFailure::NotResolved {
+                    module_id,
+                    partial_res: Some(ty_res),
+                    unresolved: item_str.into(),
+                }
+                .into())
             }
         })
     }
@@ -1133,6 +1091,8 @@ impl LinkCollector<'_, '_> {
                         // 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 };
+                            // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`
+                            // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach
                             for &new_ns in &[other_ns, MacroNS] {
                                 if let Some(res) = self.check_full_res(
                                     new_ns,
@@ -1535,7 +1495,6 @@ fn resolution_failure(
         dox,
         &link_range,
         |diag, sp| {
-            let in_scope = kinds.iter().any(|kind| kind.res().is_some());
             let item = |res: Res| {
                 format!(
                     "the {} `{}`",
@@ -1556,53 +1515,142 @@ fn resolution_failure(
             // 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, *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;
+
+                if let ResolutionFailure::NotResolved { module_id, partial_res, unresolved } =
+                    &mut failure
+                {
+                    use DefKind::*;
+
+                    let module_id = *module_id;
+                    // FIXME(jynelson): this might conflict with my `Self` fix in #76467
+                    fn split(path: &str) -> Option<(&str, &str)> {
+                        let mut splitter = path.rsplitn(2, "::");
+                        splitter.next().and_then(|right| splitter.next().map(|left| (left, right)))
+                    }
+
+                    // 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.
+                    let mut name = path_str;
+                    'outer: loop {
+                        let (start, end) = if let Some(x) = split(name) {
+                            x
+                        } else {
+                            // avoid bug that marked [Quux::Z] as missing Z, not Quux
+                            if partial_res.is_none() {
+                                *unresolved = name.into();
+                            }
+                            break;
+                        };
+                        name = start;
+                        for &ns in &[TypeNS, ValueNS, MacroNS] {
+                            if let Some(res) =
+                                collector.check_full_res(ns, &start, module_id, &None, &None)
+                            {
+                                debug!("found partial_res={:?}", res);
+                                *partial_res = Some(res);
+                                *unresolved = end.into();
+                                break 'outer;
+                            }
                         }
+                        *unresolved = end.into();
+                    }
+
+                    let last_found_module = match *partial_res {
+                        Some(Res::Def(DefKind::Mod, id)) => Some(id),
+                        None => Some(module_id),
+                        _ => None,
+                    };
+                    // See if this was a module: `[path]` or `[std::io::nope]`
+                    if let Some(module) = last_found_module {
                         // 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);
+                        let module_name = collector.cx.tcx.item_name(module);
+                        let note = format!(
+                            "the module `{}` contains no item named `{}`",
+                            module_name, unresolved
+                        );
                         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 the link has `::` in it, assume it was meant to be an intra-doc link.
+                        // Otherwise, the `[]` might be unrelated.
+                        // FIXME: don't show this for autolinks (`<>`), `()` style links, or reference links
                         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;
                     }
+
+                    // Otherwise, it must be an associated item or variant
+                    let res = partial_res.expect("None case was handled by `last_found_module`");
+                    let diagnostic_name;
+                    let (kind, name) = match res {
+                        Res::Def(kind, def_id) => {
+                            diagnostic_name = collector.cx.tcx.item_name(def_id).as_str();
+                            (Some(kind), &*diagnostic_name)
+                        }
+                        Res::PrimTy(_) => (None, name),
+                        _ => unreachable!("only ADTs and primitives are in scope at module level"),
+                    };
+                    let path_description = if let Some(kind) = kind {
+                        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"),
+                        }
+                    } else {
+                        res.descr()
+                    };
+                    let note = format!(
+                        "the {} `{}` has no {} named `{}`",
+                        res.descr(),
+                        name,
+                        disambiguator.map_or(path_description, |d| d.descr()),
+                        unresolved,
+                    );
+                    if let Some(span) = sp {
+                        diag.span_label(span, &note);
+                    } else {
+                        diag.note(&note);
+                    }
+
+                    continue;
+                }
+                let note = match failure {
+                    ResolutionFailure::NotResolved { .. } => unreachable!("handled above"),
                     ResolutionFailure::Dummy => continue,
                     ResolutionFailure::WrongNamespace(res, expected_ns) => {
                         if let Res::Def(kind, _) = res {
@@ -1637,69 +1685,6 @@ fn resolution_failure(
                             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);
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 5020b97b2f2..33260fa0e1e 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]
-   |      ^^ no item named `v2` in `deny_intra_link_resolution_failure`
+   |      ^^ the module `deny_intra_link_resolution_failure` contains no item named `v2`
    |
 note: the lint level is defined here
   --> $DIR/deny-intra-link-resolution-failure.rs:1:9
diff --git a/src/test/rustdoc-ui/intra-link-errors.rs b/src/test/rustdoc-ui/intra-link-errors.rs
index 26b629b1313..8c250fbc58b 100644
--- a/src/test/rustdoc-ui/intra-link-errors.rs
+++ b/src/test/rustdoc-ui/intra-link-errors.rs
@@ -6,19 +6,23 @@
 
 /// [path::to::nonexistent::module]
 //~^ ERROR unresolved link
-//~| NOTE no item named `path` in `intra_link_errors`
+//~| NOTE `intra_link_errors` contains no item named `path`
 
 /// [path::to::nonexistent::macro!]
 //~^ ERROR unresolved link
-//~| NOTE no item named `path` in `intra_link_errors`
+//~| NOTE `intra_link_errors` contains no item named `path`
 
 /// [type@path::to::nonexistent::type]
 //~^ ERROR unresolved link
-//~| NOTE no item named `path` in `intra_link_errors`
+//~| NOTE `intra_link_errors` contains no item named `path`
 
 /// [std::io::not::here]
 //~^ ERROR unresolved link
-//~| NOTE the module `io` has no inner item
+//~| NOTE `io` contains no item named `not`
+
+/// [type@std::io::not::here]
+//~^ ERROR unresolved link
+//~| NOTE `io` contains no item named `not`
 
 /// [std::io::Error::x]
 //~^ ERROR unresolved link
@@ -32,6 +36,10 @@
 //~^ ERROR unresolved link
 //~| NOTE `f` is a function, not a module
 
+/// [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
diff --git a/src/test/rustdoc-ui/intra-link-errors.stderr b/src/test/rustdoc-ui/intra-link-errors.stderr
index fbf3dcbbec2..cd06ee6f798 100644
--- a/src/test/rustdoc-ui/intra-link-errors.stderr
+++ b/src/test/rustdoc-ui/intra-link-errors.stderr
@@ -2,7 +2,7 @@ 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`
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the module `intra_link_errors` contains no item named `path`
    |
 note: the lint level is defined here
   --> $DIR/intra-link-errors.rs:1:9
@@ -14,64 +14,79 @@ 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`
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the module `intra_link_errors` contains no item named `path`
 
 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`
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the module `intra_link_errors` contains no item named `path`
 
 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`
+   |      ^^^^^^^^^^^^^^^^^^ the module `io` contains no item named `not`
 
-error: unresolved link to `std::io::Error::x`
+error: unresolved link to `std::io::not::here`
   --> $DIR/intra-link-errors.rs:23:6
    |
+LL | /// [type@std::io::not::here]
+   |      ^^^^^^^^^^^^^^^^^^^^^^^ the module `io` contains no item named `not`
+
+error: unresolved link to `std::io::Error::x`
+  --> $DIR/intra-link-errors.rs:27: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
+  --> $DIR/intra-link-errors.rs:31: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
+  --> $DIR/intra-link-errors.rs:35:6
    |
 LL | /// [f::A]
    |      ^^^^ `f` is a function, not a module or type, and cannot have associated items
 
+error: unresolved link to `f::A`
+  --> $DIR/intra-link-errors.rs:39: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
+  --> $DIR/intra-link-errors.rs:43: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
+  --> $DIR/intra-link-errors.rs:47: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
+  --> $DIR/intra-link-errors.rs:51: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
+  --> $DIR/intra-link-errors.rs:55:6
    |
 LL | /// [u8::not_found]
-   |      ^^^^^^^^^^^^^ the builtin type `u8` does not have an associated item named `not_found`
+   |      ^^^^^^^^^^^^^
+   |      |
+   |      the builtin type `u8` does not have an associated item named `not_found`
+   |      the builtin type `u8` has no builtin type named `not_found`
 
 error: unresolved link to `S`
-  --> $DIR/intra-link-errors.rs:51:6
+  --> $DIR/intra-link-errors.rs:59:6
    |
 LL | /// [S!]
    |      ^^
@@ -80,7 +95,7 @@ LL | /// [S!]
    |      help: to link to the struct, prefix with `struct@`: `struct@S`
 
 error: unresolved link to `T::g`
-  --> $DIR/intra-link-errors.rs:69:6
+  --> $DIR/intra-link-errors.rs:77:6
    |
 LL | /// [type@T::g]
    |      ^^^^^^^^^
@@ -89,13 +104,13 @@ LL | /// [type@T::g]
    |      help: to link to the associated function, add parentheses: `T::g()`
 
 error: unresolved link to `T::h`
-  --> $DIR/intra-link-errors.rs:74:6
+  --> $DIR/intra-link-errors.rs:82: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
+  --> $DIR/intra-link-errors.rs:69:6
    |
 LL | /// [type@S::h]
    |      ^^^^^^^^^
@@ -104,7 +119,7 @@ LL | /// [type@S::h]
    |      help: to link to the associated function, add parentheses: `S::h()`
 
 error: unresolved link to `m`
-  --> $DIR/intra-link-errors.rs:81:6
+  --> $DIR/intra-link-errors.rs:89:6
    |
 LL | /// [m()]
    |      ^^^
@@ -112,5 +127,5 @@ 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
+error: aborting due to 18 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 3c13df20588..d946aa93980 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])
-   |           ^ no item named `i` in `intra_link_span_ice_55723`
+   |           ^ the module `intra_link_span_ice_55723` contains no item named `i`
    |
 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-warning-crlf.stderr b/src/test/rustdoc-ui/intra-links-warning-crlf.stderr
index 351f8fafa64..76a2ac0c8cf 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]
-   |      ^^^^^ no item named `error` in `intra_links_warning_crlf`
+   |      ^^^^^ the module `intra_links_warning_crlf` contains no item named `error`
    |
    = 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]
-   |           ^^^^^^ no item named `error1` in `intra_links_warning_crlf`
+   |           ^^^^^^ the module `intra_links_warning_crlf` contains no item named `error1`
    |
    = 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]
-   |           ^^^^^^ no item named `error2` in `intra_links_warning_crlf`
+   |           ^^^^^^ the module `intra_links_warning_crlf` contains no item named `error2`
    |
    = 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].
-   |                    ^^^^^ no item named `error` in `intra_links_warning_crlf`
+   |                    ^^^^^ the module `intra_links_warning_crlf` contains no item named `error`
    |
    = 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 0832e00d35a..09db465df59 100644
--- a/src/test/rustdoc-ui/intra-links-warning.stderr
+++ b/src/test/rustdoc-ui/intra-links-warning.stderr
@@ -10,37 +10,37 @@ warning: unresolved link to `Bar::foo`
   --> $DIR/intra-links-warning.rs:3:35
    |
 LL |        //! Test with [Foo::baz], [Bar::foo], ...
-   |                                   ^^^^^^^^ no item named `Bar` in `intra_links_warning`
+   |                                   ^^^^^^^^ the module `intra_links_warning` contains no item named `Bar`
 
 warning: unresolved link to `Uniooon::X`
   --> $DIR/intra-links-warning.rs:6:13
    |
 LL |      //! , [Uniooon::X] and [Qux::Z].
-   |             ^^^^^^^^^^ no item named `Uniooon` in `intra_links_warning`
+   |             ^^^^^^^^^^ the module `intra_links_warning` contains no item named `Uniooon`
 
 warning: unresolved link to `Qux::Z`
   --> $DIR/intra-links-warning.rs:6:30
    |
 LL |      //! , [Uniooon::X] and [Qux::Z].
-   |                              ^^^^^^ no item named `Qux` in `intra_links_warning`
+   |                              ^^^^^^ the module `intra_links_warning` contains no item named `Qux`
 
 warning: unresolved link to `Uniooon::X`
   --> $DIR/intra-links-warning.rs:10:14
    |
 LL |       //! , [Uniooon::X] and [Qux::Z].
-   |              ^^^^^^^^^^ no item named `Uniooon` in `intra_links_warning`
+   |              ^^^^^^^^^^ the module `intra_links_warning` contains no item named `Uniooon`
 
 warning: unresolved link to `Qux::Z`
   --> $DIR/intra-links-warning.rs:10:31
    |
 LL |       //! , [Uniooon::X] and [Qux::Z].
-   |                               ^^^^^^ no item named `Qux` in `intra_links_warning`
+   |                               ^^^^^^ the module `intra_links_warning` contains no item named `Qux`
 
 warning: unresolved link to `Qux:Y`
   --> $DIR/intra-links-warning.rs:14:13
    |
 LL |        /// [Qux:Y]
-   |             ^^^^^ no item named `Qux:Y` in `intra_links_warning`
+   |             ^^^^^ the module `intra_links_warning` contains no item named `Qux:Y`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -48,7 +48,7 @@ warning: unresolved link to `error`
   --> $DIR/intra-links-warning.rs:58:30
    |
 LL |  * time to introduce a link [error]*/
-   |                              ^^^^^ no item named `error` in `intra_links_warning`
+   |                              ^^^^^ the module `intra_links_warning` contains no item named `error`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -56,7 +56,7 @@ warning: unresolved link to `error`
   --> $DIR/intra-links-warning.rs:64:30
    |
 LL |  * time to introduce a link [error]
-   |                              ^^^^^ no item named `error` in `intra_links_warning`
+   |                              ^^^^^ the module `intra_links_warning` contains no item named `error`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -70,7 +70,7 @@ LL | #[doc = "single line [error]"]
            
            single line [error]
                         ^^^^^
-   = note: no item named `error` in `intra_links_warning`
+   = note: the module `intra_links_warning` contains no item named `error`
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 warning: unresolved link to `error`
@@ -83,7 +83,7 @@ LL | #[doc = "single line with \"escaping\" [error]"]
            
            single line with "escaping" [error]
                                         ^^^^^
-   = note: no item named `error` in `intra_links_warning`
+   = note: the module `intra_links_warning` contains no item named `error`
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 warning: unresolved link to `error`
@@ -98,14 +98,14 @@ LL | | /// [error]
            
            [error]
             ^^^^^
-   = note: no item named `error` in `intra_links_warning`
+   = note: the module `intra_links_warning` contains no item named `error`
    = 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]
-   |           ^^^^^^ no item named `error1` in `intra_links_warning`
+   |           ^^^^^^ the module `intra_links_warning` contains no item named `error1`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -113,7 +113,7 @@ warning: unresolved link to `error2`
   --> $DIR/intra-links-warning.rs:82:11
    |
 LL | /// docs [error2]
-   |           ^^^^^^ no item named `error2` in `intra_links_warning`
+   |           ^^^^^^ the module `intra_links_warning` contains no item named `error2`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -121,7 +121,7 @@ warning: unresolved link to `BarA`
   --> $DIR/intra-links-warning.rs:21:10
    |
 LL | /// bar [BarA] bar
-   |          ^^^^ no item named `BarA` in `intra_links_warning`
+   |          ^^^^ the module `intra_links_warning` contains no item named `BarA`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -129,7 +129,7 @@ warning: unresolved link to `BarB`
   --> $DIR/intra-links-warning.rs:27:9
    |
 LL |  * bar [BarB] bar
-   |         ^^^^ no item named `BarB` in `intra_links_warning`
+   |         ^^^^ the module `intra_links_warning` contains no item named `BarB`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -137,7 +137,7 @@ warning: unresolved link to `BarC`
   --> $DIR/intra-links-warning.rs:34:6
    |
 LL | bar [BarC] bar
-   |      ^^^^ no item named `BarC` in `intra_links_warning`
+   |      ^^^^ the module `intra_links_warning` contains no item named `BarC`
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
@@ -151,7 +151,7 @@ LL | #[doc = "Foo\nbar [BarD] bar\nbaz"]
            
            bar [BarD] bar
                 ^^^^
-   = note: no item named `BarD` in `intra_links_warning`
+   = note: the module `intra_links_warning` contains no item named `BarD`
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
 
 warning: unresolved link to `BarF`
@@ -167,7 +167,7 @@ LL | f!("Foo\nbar [BarF] bar\nbaz");
            
            bar [BarF] bar
                 ^^^^
-   = note: no item named `BarF` in `intra_links_warning`
+   = note: the module `intra_links_warning` contains no item named `BarF`
    = 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 550b79f6e89..4e9134ea469 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]
-   |                             ^^^^^ no item named `error` in `lint_group`
+   |                             ^^^^^ the module `lint_group` contains no item named `error`
    |
 note: the lint level is defined here
   --> $DIR/lint-group.rs:7:9
diff --git a/src/test/rustdoc/intra-link-associated-items.rs b/src/test/rustdoc/intra-link-associated-items.rs
index 16a21e33748..dec0deae5df 100644
--- a/src/test/rustdoc/intra-link-associated-items.rs
+++ b/src/test/rustdoc/intra-link-associated-items.rs
@@ -3,8 +3,10 @@
 
 /// [`std::collections::BTreeMap::into_iter`]
 /// [`String::from`] is ambiguous as to which `From` impl
+/// [type@Vec::into_iter] uses a disambiguator
 // @has 'intra_link_associated_items/fn.foo.html' '//a[@href="https://doc.rust-lang.org/nightly/alloc/collections/btree/map/struct.BTreeMap.html#method.into_iter"]' 'std::collections::BTreeMap::into_iter'
 // @has 'intra_link_associated_items/fn.foo.html' '//a[@href="https://doc.rust-lang.org/nightly/alloc/string/struct.String.html#method.from"]' 'String::from'
+// @has 'intra_link_associated_items/fn.foo.html' '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.into_iter"]' 'Vec::into_iter'
 pub fn foo() {}
 
 /// Link to [MyStruct], [link from struct][MyStruct::method], [MyStruct::clone], [MyStruct::Input]