about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs340
1 files changed, 198 insertions, 142 deletions
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 789523c561e..7e24318dec8 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -47,7 +47,18 @@ fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
     krate
 }
 
-#[derive(Copy, Clone, Debug, Hash)]
+fn filter_assoc_items_by_name_and_namespace<'a>(
+    tcx: TyCtxt<'a>,
+    assoc_items_of: DefId,
+    ident: Ident,
+    ns: Namespace,
+) -> impl Iterator<Item = &ty::AssocItem> + 'a {
+    tcx.associated_items(assoc_items_of).filter_by_name_unhygienic(ident.name).filter(move |item| {
+        item.kind.namespace() == ns && tcx.hygienic_eq(ident, item.ident(tcx), assoc_items_of)
+    })
+}
+
+#[derive(Copy, Clone, Debug, Hash, PartialEq)]
 enum Res {
     Def(DefKind, DefId),
     Primitive(PrimitiveType),
@@ -317,14 +328,21 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         prim_ty: PrimitiveType,
         ns: Namespace,
         item_name: Symbol,
-    ) -> Option<(Res, DefId)> {
+    ) -> Vec<(Res, DefId)> {
         let tcx = self.cx.tcx;
 
-        prim_ty.impls(tcx).find_map(|impl_| {
-            tcx.associated_items(impl_)
-                .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, impl_)
+        prim_ty
+            .impls(tcx)
+            .flat_map(|impl_| {
+                filter_assoc_items_by_name_and_namespace(
+                    tcx,
+                    impl_,
+                    Ident::with_dummy_span(item_name),
+                    ns,
+                )
                 .map(|item| (Res::Primitive(prim_ty), item.def_id))
-        })
+            })
+            .collect::<Vec<_>>()
     }
 
     fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: DefId) -> Option<Res> {
@@ -394,14 +412,16 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         ns: Namespace,
         item_id: DefId,
         module_id: DefId,
-    ) -> Result<(Res, Option<DefId>), UnresolvedPath<'path>> {
+    ) -> Result<Vec<(Res, Option<DefId>)>, UnresolvedPath<'path>> {
         if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) {
             return Ok(match res {
                 Res::Def(
                     DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy | DefKind::Variant,
                     def_id,
-                ) => (Res::from_def_id(self.cx.tcx, self.cx.tcx.parent(def_id)), Some(def_id)),
-                _ => (res, None),
+                ) => {
+                    vec![(Res::from_def_id(self.cx.tcx, self.cx.tcx.parent(def_id)), Some(def_id))]
+                }
+                _ => vec![(res, None)],
             });
         } else if ns == MacroNS {
             return Err(UnresolvedPath {
@@ -436,14 +456,21 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         // links to primitives when `#[doc(primitive)]` is present. It should give an ambiguity
         // error instead and special case *only* modules with `#[doc(primitive)]`, not all
         // primitives.
-        resolve_primitive(&path_root, TypeNS)
+        match resolve_primitive(&path_root, TypeNS)
             .or_else(|| self.resolve_path(&path_root, TypeNS, item_id, module_id))
             .and_then(|ty_res| {
-                self.resolve_associated_item(ty_res, item_name, ns, module_id).map(Ok)
-            })
-            .unwrap_or_else(|| {
+                let candidates = self
+                    .resolve_associated_item(ty_res, item_name, ns, module_id)
+                    .into_iter()
+                    .map(|(res, def_id)| (res, Some(def_id)))
+                    .collect::<Vec<_>>();
+                if !candidates.is_empty() { Some(candidates) } else { None }
+            }) {
+            Some(r) => Ok(r),
+            None => {
                 if ns == Namespace::ValueNS {
                     self.variant_field(path_str, item_id, module_id)
+                        .map(|(res, def_id)| vec![(res, Some(def_id))])
                 } else {
                     Err(UnresolvedPath {
                         item_id,
@@ -452,8 +479,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         unresolved: path_root.into(),
                     })
                 }
-            })
-            .map(|(res, def_id)| (res, Some(def_id)))
+            }
+        }
     }
 
     /// Convert a DefId to a Res, where possible.
@@ -535,24 +562,31 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         item_name: Symbol,
         ns: Namespace,
         module_id: DefId,
-    ) -> Option<(Res, DefId)> {
+    ) -> Vec<(Res, DefId)> {
         let tcx = self.cx.tcx;
 
         match root_res {
             Res::Primitive(prim) => {
-                self.resolve_primitive_associated_item(prim, ns, item_name).or_else(|| {
+                let items = self.resolve_primitive_associated_item(prim, ns, item_name);
+                if !items.is_empty() {
+                    items
+                // Inherent associated items take precedence over items that come from trait impls.
+                } else {
                     self.primitive_type_to_ty(prim)
-                        .and_then(|ty| {
+                        .map(|ty| {
                             resolve_associated_trait_item(ty, module_id, item_name, ns, self.cx)
+                                .iter()
+                                .map(|item| (root_res, item.def_id))
+                                .collect::<Vec<_>>()
                         })
-                        .map(|item| (root_res, item.def_id))
-                })
+                        .unwrap_or(Vec::new())
+                }
             }
             Res::Def(DefKind::TyAlias, did) => {
                 // Resolve the link on the type the alias points to.
                 // FIXME: if the associated item is defined directly on the type alias,
                 // it will show up on its documentation page, we should link there instead.
-                let res = self.def_id_to_res(did)?;
+                let Some(res) = self.def_id_to_res(did) else { return Vec::new() };
                 self.resolve_associated_item(res, item_name, ns, module_id)
             }
             Res::Def(
@@ -566,7 +600,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         ty::Adt(adt_def, _) => {
                             for variant in adt_def.variants() {
                                 if variant.name == item_name {
-                                    return Some((root_res, variant.def_id));
+                                    return vec![(root_res, variant.def_id)];
                                 }
                             }
                         }
@@ -575,43 +609,46 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 }
 
                 // Checks if item_name belongs to `impl SomeItem`
-                let assoc_item = tcx
+                let mut assoc_items: Vec<_> = tcx
                     .inherent_impls(did)
                     .iter()
                     .flat_map(|&imp| {
-                        tcx.associated_items(imp).find_by_name_and_namespace(
+                        filter_assoc_items_by_name_and_namespace(
                             tcx,
+                            imp,
                             Ident::with_dummy_span(item_name),
                             ns,
-                            imp,
                         )
                     })
-                    .copied()
-                    // There should only ever be one associated item that matches from any inherent impl
-                    .next()
+                    .map(|item| (root_res, item.def_id))
+                    .collect();
+
+                if assoc_items.is_empty() {
                     // Check if item_name belongs to `impl SomeTrait for SomeItem`
                     // FIXME(#74563): This gives precedence to `impl SomeItem`:
                     // Although having both would be ambiguous, use impl version for compatibility's sake.
                     // To handle that properly resolve() would have to support
                     // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
-                    .or_else(|| {
-                        resolve_associated_trait_item(
-                            tcx.type_of(did).subst_identity(),
-                            module_id,
-                            item_name,
-                            ns,
-                            self.cx,
-                        )
-                    });
+                    assoc_items = resolve_associated_trait_item(
+                        tcx.type_of(did).subst_identity(),
+                        module_id,
+                        item_name,
+                        ns,
+                        self.cx,
+                    )
+                    .into_iter()
+                    .map(|item| (root_res, item.def_id))
+                    .collect::<Vec<_>>();
+                }
 
-                debug!("got associated item {:?}", assoc_item);
+                debug!("got associated item {:?}", assoc_items);
 
-                if let Some(item) = assoc_item {
-                    return Some((root_res, item.def_id));
+                if !assoc_items.is_empty() {
+                    return assoc_items;
                 }
 
                 if ns != Namespace::ValueNS {
-                    return None;
+                    return Vec::new();
                 }
                 debug!("looking for fields named {} for {:?}", item_name, did);
                 // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?)
@@ -631,20 +668,27 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 // field syntax) and are handled by the compiler's resolver.
                 let def = match tcx.type_of(did).subst_identity().kind() {
                     ty::Adt(def, _) if !def.is_enum() => def,
-                    _ => return None,
+                    _ => return Vec::new(),
                 };
-                let field =
-                    def.non_enum_variant().fields.iter().find(|item| item.name == item_name)?;
-                Some((root_res, field.did))
+                def.non_enum_variant()
+                    .fields
+                    .iter()
+                    .filter(|field| field.name == item_name)
+                    .map(|field| (root_res, field.did))
+                    .collect::<Vec<_>>()
             }
-            Res::Def(DefKind::Trait, did) => tcx
-                .associated_items(did)
-                .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, did)
-                .map(|item| {
-                    let res = Res::Def(item.kind.as_def_kind(), item.def_id);
-                    (res, item.def_id)
-                }),
-            _ => None,
+            Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace(
+                tcx,
+                did,
+                Ident::with_dummy_span(item_name),
+                ns,
+            )
+            .map(|item| {
+                let res = Res::Def(item.kind.as_def_kind(), item.def_id);
+                (res, item.def_id)
+            })
+            .collect::<Vec<_>>(),
+            _ => Vec::new(),
         }
     }
 }
@@ -664,7 +708,7 @@ fn resolve_associated_trait_item<'a>(
     item_name: Symbol,
     ns: Namespace,
     cx: &mut DocContext<'a>,
-) -> Option<ty::AssocItem> {
+) -> Vec<ty::AssocItem> {
     // FIXME: this should also consider blanket impls (`impl<T> X for T`). Unfortunately
     // `get_auto_trait_and_blanket_impls` is broken because the caching behavior is wrong. In the
     // meantime, just don't look for these blanket impls.
@@ -672,19 +716,26 @@ fn resolve_associated_trait_item<'a>(
     // Next consider explicit impls: `impl MyTrait for MyType`
     // Give precedence to inherent impls.
     let traits = trait_impls_for(cx, ty, module);
+    let tcx = cx.tcx;
     debug!("considering traits {:?}", traits);
-    let mut candidates = traits.iter().filter_map(|&(impl_, trait_)| {
-        cx.tcx
-            .associated_items(trait_)
-            .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_)
-            .map(|trait_assoc| {
-                trait_assoc_to_impl_assoc_item(cx.tcx, impl_, trait_assoc.def_id)
+    let candidates = traits
+        .iter()
+        .flat_map(|&(impl_, trait_)| {
+            filter_assoc_items_by_name_and_namespace(
+                cx.tcx,
+                trait_,
+                Ident::with_dummy_span(item_name),
+                ns,
+            )
+            .map(move |trait_assoc| {
+                trait_assoc_to_impl_assoc_item(tcx, impl_, trait_assoc.def_id)
                     .unwrap_or(*trait_assoc)
             })
-    });
+        })
+        .collect::<Vec<_>>();
     // FIXME(#74563): warn about ambiguity
-    debug!("the candidates were {:?}", candidates.clone().collect::<Vec<_>>());
-    candidates.next()
+    debug!("the candidates were {:?}", candidates);
+    candidates
 }
 
 /// Find the associated item in the impl `impl_id` that corresponds to the
@@ -755,20 +806,6 @@ fn trait_impls_for<'a>(
     iter.collect()
 }
 
-/// 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<Result<(Res, T), ResolutionFailure<'_>>>) -> bool {
-    matches!(
-        *ns,
-        PerNS {
-            type_ns: Ok((Res::Def(DefKind::Trait, _), _)),
-            macro_ns: Ok((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)),
-            ..
-        }
-    )
-}
-
 impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
     fn visit_item(&mut self, item: &Item) {
         self.resolve_links(item);
@@ -978,7 +1015,7 @@ impl LinkCollector<'_, '_> {
                     res = prim;
                 } else {
                     // `[char]` when a `char` module is in scope
-                    let candidates = vec![res, prim];
+                    let candidates = vec![(res, res.def_id(self.cx.tcx)), (prim, None)];
                     ambiguity_error(self.cx, diag_info, path_str, candidates);
                     return None;
                 }
@@ -986,7 +1023,7 @@ impl LinkCollector<'_, '_> {
         }
 
         match res {
-            Res::Primitive(prim) => {
+            Res::Primitive(_) => {
                 if let Some(UrlFragment::Item(id)) = fragment {
                     // We're actually resolving an associated item of a primitive, so we need to
                     // verify the disambiguator (if any) matches the type of the associated item.
@@ -1006,15 +1043,6 @@ impl LinkCollector<'_, '_> {
                         item,
                         &diag_info,
                     )?;
-
-                    // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
-                    // However I'm not sure how to check that across crates.
-                    if prim == PrimitiveType::RawPointer
-                        && item.item_id.is_local()
-                        && !self.cx.tcx.features().intra_doc_pointers
-                    {
-                        self.report_rawptr_assoc_feature_gate(dox, ori_link, item);
-                    }
                 } else {
                     match disambiguator {
                         Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {}
@@ -1135,10 +1163,9 @@ impl LinkCollector<'_, '_> {
         report_diagnostic(self.cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, diag_info, callback);
     }
 
-    fn report_rawptr_assoc_feature_gate(&self, dox: &str, ori_link: &MarkdownLink, item: &Item) {
-        let span =
-            super::source_span_for_markdown_range(self.cx.tcx, dox, &ori_link.range, &item.attrs)
-                .unwrap_or_else(|| item.attr_span(self.cx.tcx));
+    fn report_rawptr_assoc_feature_gate(&self, dox: &str, ori_link: &Range<usize>, item: &Item) {
+        let span = super::source_span_for_markdown_range(self.cx.tcx, dox, ori_link, &item.attrs)
+            .unwrap_or_else(|| item.attr_span(self.cx.tcx));
         rustc_session::parse::feature_err(
             &self.cx.tcx.sess.parse_sess,
             sym::intra_doc_pointers,
@@ -1163,7 +1190,23 @@ impl LinkCollector<'_, '_> {
             }
         }
 
-        let res = self.resolve_with_disambiguator(&key, diag.clone()).and_then(|(res, def_id)| {
+        let mut candidates = self.resolve_with_disambiguator(&key, diag.clone());
+
+        // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
+        // However I'm not sure how to check that across crates.
+        if let Some(candidate) = candidates.get(0) &&
+            candidate.0 == Res::Primitive(PrimitiveType::RawPointer) &&
+            key.path_str.contains("::") // We only want to check this if this is an associated item.
+        {
+            if key.item_id.is_local() && !self.cx.tcx.features().intra_doc_pointers {
+                self.report_rawptr_assoc_feature_gate(diag.dox, &diag.link_range, diag.item);
+                return None;
+            } else {
+                candidates = vec![candidates[0]];
+            }
+        }
+
+        if let &[(res, def_id)] = candidates.as_slice() {
             let fragment = match (&key.extra_fragment, def_id) {
                 (Some(_), Some(def_id)) => {
                     report_anchor_conflict(self.cx, diag, def_id);
@@ -1173,13 +1216,18 @@ impl LinkCollector<'_, '_> {
                 (None, Some(def_id)) => Some(UrlFragment::Item(def_id)),
                 (None, None) => None,
             };
-            Some((res, fragment))
-        });
+            let r = Some((res, fragment));
+            self.visited_links.insert(key, r.clone());
+            return r;
+        }
 
-        if res.is_some() || cache_errors {
-            self.visited_links.insert(key, res.clone());
+        if !candidates.is_empty() {
+            ambiguity_error(self.cx, diag, &key.path_str, candidates);
         }
-        res
+        if cache_errors {
+            self.visited_links.insert(key, None);
+        }
+        None
     }
 
     /// After parsing the disambiguator, resolve the main part of the link.
@@ -1188,7 +1236,7 @@ impl LinkCollector<'_, '_> {
         &mut self,
         key: &ResolutionInfo,
         diag: DiagnosticInfo<'_>,
-    ) -> Option<(Res, Option<DefId>)> {
+    ) -> Vec<(Res, Option<DefId>)> {
         let disambiguator = key.dis;
         let path_str = &key.path_str;
         let item_id = key.item_id;
@@ -1197,7 +1245,7 @@ impl LinkCollector<'_, '_> {
         match disambiguator.map(Disambiguator::ns) {
             Some(expected_ns) => {
                 match self.resolve(path_str, expected_ns, item_id, module_id) {
-                    Ok(res) => Some(res),
+                    Ok(candidates) => candidates,
                     Err(err) => {
                         // We only looked in one namespace. Try to give a better error if possible.
                         // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`.
@@ -1206,10 +1254,11 @@ impl LinkCollector<'_, '_> {
                         for other_ns in [TypeNS, ValueNS, MacroNS] {
                             if other_ns != expected_ns {
                                 if let Ok(res) =
-                                    self.resolve(path_str, other_ns, item_id, module_id)
+                                    self.resolve(path_str, other_ns, item_id, module_id) &&
+                                    !res.is_empty()
                                 {
                                     err = ResolutionFailure::WrongNamespace {
-                                        res: full_res(self.cx.tcx, res),
+                                        res: full_res(self.cx.tcx, res[0]),
                                         expected_ns,
                                     };
                                     break;
@@ -1230,18 +1279,26 @@ impl LinkCollector<'_, '_> {
                 let candidates = PerNS {
                     macro_ns: candidate(MacroNS),
                     type_ns: candidate(TypeNS),
-                    value_ns: candidate(ValueNS).and_then(|(res, def_id)| {
-                        match res {
-                            // Constructors are picked up in the type namespace.
-                            Res::Def(DefKind::Ctor(..), _) => {
-                                Err(ResolutionFailure::WrongNamespace { res, expected_ns: TypeNS })
+                    value_ns: candidate(ValueNS).and_then(|v_res| {
+                        for (res, _) in v_res.iter() {
+                            match res {
+                                // Constructors are picked up in the type namespace.
+                                Res::Def(DefKind::Ctor(..), _) => {
+                                    return Err(ResolutionFailure::WrongNamespace {
+                                        res: *res,
+                                        expected_ns: TypeNS,
+                                    });
+                                }
+                                _ => {}
                             }
-                            _ => Ok((res, def_id)),
                         }
+                        Ok(v_res)
                     }),
                 };
 
-                let len = candidates.iter().filter(|res| res.is_ok()).count();
+                let len = candidates
+                    .iter()
+                    .fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc });
 
                 if len == 0 {
                     return resolution_failure(
@@ -1253,21 +1310,7 @@ impl LinkCollector<'_, '_> {
                     );
                 }
 
-                if len == 1 {
-                    Some(candidates.into_iter().find_map(|res| res.ok()).unwrap())
-                } else if len == 2 && is_derive_trait_collision(&candidates) {
-                    Some(candidates.type_ns.unwrap())
-                } else {
-                    let ignore_macro = is_derive_trait_collision(&candidates);
-                    // If we're reporting an ambiguity, don't mention the namespaces that failed
-                    let mut candidates =
-                        candidates.map(|candidate| candidate.ok().map(|(res, _)| res));
-                    if ignore_macro {
-                        candidates.macro_ns = None;
-                    }
-                    ambiguity_error(self.cx, diag, path_str, candidates.present_items().collect());
-                    None
-                }
+                candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>()
             }
         }
     }
@@ -1554,7 +1597,7 @@ fn resolution_failure(
     path_str: &str,
     disambiguator: Option<Disambiguator>,
     kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
-) -> Option<(Res, Option<DefId>)> {
+) -> Vec<(Res, Option<DefId>)> {
     let tcx = collector.cx.tcx;
     let mut recovered_res = None;
     report_diagnostic(
@@ -1613,11 +1656,13 @@ fn resolution_failure(
                         };
                         name = start;
                         for ns in [TypeNS, ValueNS, MacroNS] {
-                            if let Ok(res) = collector.resolve(start, ns, item_id, module_id) {
-                                debug!("found partial_res={:?}", res);
-                                *partial_res = Some(full_res(collector.cx.tcx, res));
-                                *unresolved = end.into();
-                                break 'outer;
+                            if let Ok(v_res) = collector.resolve(start, ns, item_id, module_id) {
+                                debug!("found partial_res={:?}", v_res);
+                                if !v_res.is_empty() {
+                                    *partial_res = Some(full_res(collector.cx.tcx, v_res[0]));
+                                    *unresolved = end.into();
+                                    break 'outer;
+                                }
                             }
                         }
                         *unresolved = end.into();
@@ -1765,7 +1810,10 @@ fn resolution_failure(
         },
     );
 
-    recovered_res
+    match recovered_res {
+        Some(r) => vec![r],
+        None => Vec::new(),
+    }
 }
 
 fn report_multiple_anchors(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) {
@@ -1854,24 +1902,32 @@ fn ambiguity_error(
     cx: &DocContext<'_>,
     diag_info: DiagnosticInfo<'_>,
     path_str: &str,
-    candidates: Vec<Res>,
+    candidates: Vec<(Res, Option<DefId>)>,
 ) {
     let mut msg = format!("`{}` is ", path_str);
+    let kinds = candidates
+        .into_iter()
+        .map(
+            |(res, def_id)| {
+                if let Some(def_id) = def_id { Res::from_def_id(cx.tcx, def_id) } else { res }
+            },
+        )
+        .collect::<Vec<_>>();
 
-    match candidates.as_slice() {
-        [first_def, second_def] => {
+    match kinds.as_slice() {
+        [res1, res2] => {
             msg += &format!(
                 "both {} {} and {} {}",
-                first_def.article(),
-                first_def.descr(),
-                second_def.article(),
-                second_def.descr(),
+                res1.article(),
+                res1.descr(),
+                res2.article(),
+                res2.descr()
             );
         }
         _ => {
-            let mut candidates = candidates.iter().peekable();
-            while let Some(res) = candidates.next() {
-                if candidates.peek().is_some() {
+            let mut kinds = kinds.iter().peekable();
+            while let Some(res) = kinds.next() {
+                if kinds.peek().is_some() {
                     msg += &format!("{} {}, ", res.article(), res.descr());
                 } else {
                     msg += &format!("and {} {}", res.article(), res.descr());
@@ -1887,7 +1943,7 @@ fn ambiguity_error(
             diag.note("ambiguous link");
         }
 
-        for res in candidates {
+        for res in kinds {
             suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp);
         }
     });