X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;ds=sidebyside;f=src%2Flibrustdoc%2Fpasses%2Fcollect_intra_doc_links.rs;h=42e87f3f9610b1b54cb855fce0679998e7cce810;hb=7689857884c8d74f25517373f2d3d4c6fe055614;hp=c1b1139ad8cf95ec5ac178662a6fbebe774c5185;hpb=d14bda48d5bdbc03a31314584bdbab0d9918cddc;p=rust.git diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index c1b1139ad8c..14929a3772a 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -3,6 +3,7 @@ //! [RFC 1946]: https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md use pulldown_cmark::LinkType; +use rustc_ast::util::comments::may_have_doc_links; use rustc_data_structures::{fx::FxHashMap, intern::Interned, stable_set::FxHashSet}; use rustc_errors::{Applicability, Diagnostic}; use rustc_hir::def::Namespace::*; @@ -10,11 +11,12 @@ use rustc_hir::def_id::{DefId, CRATE_DEF_ID}; use rustc_hir::Mutability; use rustc_middle::ty::{DefIdTree, Ty, TyCtxt}; -use rustc_middle::{bug, span_bug, ty}; +use rustc_middle::{bug, ty}; +use rustc_resolve::ParentScope; use rustc_session::lint::Lint; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{sym, Ident, Symbol}; -use rustc_span::{BytePos, DUMMY_SP}; +use rustc_span::BytePos; use smallvec::{smallvec, SmallVec}; use std::borrow::Cow; @@ -46,18 +48,6 @@ fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate { krate } -/// Top-level errors emitted by this pass. -enum ErrorKind<'a> { - Resolve(Box>), - AnchorFailure(AnchorFailure), -} - -impl<'a> From> for ErrorKind<'a> { - fn from(err: ResolutionFailure<'a>) -> Self { - ErrorKind::Resolve(box err) - } -} - #[derive(Copy, Clone, Debug, Hash)] enum Res { Def(DefKind, DefId), @@ -95,12 +85,8 @@ fn def_id(self, tcx: TyCtxt<'_>) -> DefId { } } - fn as_hir_res(self) -> Option { - match self { - Res::Def(kind, id) => Some(rustc_hir::def::Res::Def(kind, id)), - // FIXME: maybe this should handle the subset of PrimitiveType that fits into hir::PrimTy? - Res::Primitive(_) => None, - } + fn from_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Res { + Res::Def(tcx.def_kind(def_id), def_id) } /// Used for error reporting. @@ -158,7 +144,24 @@ fn try_from(res: ResolveRes) -> Result { } } -/// A link failed to resolve. +/// The link failed to resolve. [`resolution_failure`] should look to see if there's +/// a more helpful error that can be given. +#[derive(Debug)] +struct UnresolvedPath<'a> { + /// Item on which the link is resolved, used for resolving `Self`. + item_id: ItemId, + /// The scope the link was resolved in. + module_id: DefId, + /// If part of the link resolved, this has the `Res`. + /// + /// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution. + partial_res: Option, + /// The remaining unresolved path segments. + /// + /// In `[std::io::Error::x]`, `x` would be unresolved. + unresolved: Cow<'a, str>, +} + #[derive(Debug)] enum ResolutionFailure<'a> { /// This resolved, but with the wrong namespace. @@ -171,35 +174,10 @@ enum ResolutionFailure<'a> { /// even though `Result`'s actual namespace is [`Namespace::TypeNS`]. expected_ns: Namespace, }, - /// The link failed to resolve. [`resolution_failure`] should look to see if there's - /// a more helpful error that can be given. - NotResolved { - /// Item on which the link is resolved, used for resolving `Self`. - item_id: ItemId, - /// The scope the link was resolved in. - module_id: DefId, - /// If part of the link resolved, this has the `Res`. - /// - /// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution. - partial_res: Option, - /// The remaining unresolved path segments. - /// - /// In `[std::io::Error::x]`, `x` would be unresolved. - unresolved: Cow<'a, str>, - }, - /// This happens when rustdoc can't determine the parent scope for an item. - /// It is always a bug in rustdoc. - NoParentItem, - /// This link has malformed generic parameters; e.g., the angle brackets are unbalanced. - MalformedGenerics(MalformedGenerics), - /// Used to communicate that this should be ignored, but shouldn't be reported to the user. - /// - /// This happens when there is no disambiguator and one of the namespaces - /// failed to resolve. - Dummy, + NotResolved(UnresolvedPath<'a>), } -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] enum MalformedGenerics { /// This link has unbalanced angle brackets. /// @@ -240,34 +218,6 @@ enum MalformedGenerics { EmptyAngleBrackets, } -impl ResolutionFailure<'_> { - /// This resolved fully (not just partially) but is erroneous for some other reason - /// - /// Returns the full resolution of the link, if present. - fn full_res(&self) -> Option { - match self { - Self::WrongNamespace { res, expected_ns: _ } => Some(*res), - _ => None, - } - } -} - -enum AnchorFailure { - /// User error: `[std#x#y]` is not valid - MultipleAnchors, - /// The anchor provided by the user conflicts with Rustdoc's generated anchor. - /// - /// This is an unfortunate state of affairs. Not every item that can be - /// linked to has its own page; sometimes it is a subheading within a page, - /// like for associated items. In those cases, rustdoc uses an anchor to - /// link to the subheading. Since you can't have two anchors for the same - /// link, Rustdoc disallows having a user-specified anchor. - /// - /// Most of the time this is fine, because you can just link to the page of - /// the item if you want to provide your own anchor. - RustdocAnchorConflict(Res), -} - #[derive(Clone, Debug, Hash, PartialEq, Eq)] crate enum UrlFragment { Item(ItemFragment), @@ -299,24 +249,32 @@ impl UrlFragment { VariantField, } -impl ItemFragment { - /// Create a fragment for an associated item. - #[instrument(level = "debug")] - fn from_assoc_item(item: &ty::AssocItem) -> Self { - let def_id = item.def_id; - match item.kind { - ty::AssocKind::Fn => { - if item.defaultness.has_value() { - ItemFragment(FragmentKind::Method, def_id) +impl FragmentKind { + fn from_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> FragmentKind { + match tcx.def_kind(def_id) { + DefKind::AssocFn => { + if tcx.associated_item(def_id).defaultness.has_value() { + FragmentKind::Method + } else { + FragmentKind::TyMethod + } + } + DefKind::AssocConst => FragmentKind::AssociatedConstant, + DefKind::AssocTy => FragmentKind::AssociatedType, + DefKind::Variant => FragmentKind::Variant, + DefKind::Field => { + if tcx.def_kind(tcx.parent(def_id).unwrap()) == DefKind::Variant { + FragmentKind::VariantField } else { - ItemFragment(FragmentKind::TyMethod, def_id) + FragmentKind::StructField } } - ty::AssocKind::Const => ItemFragment(FragmentKind::AssociatedConstant, def_id), - ty::AssocKind::Type => ItemFragment(FragmentKind::AssociatedType, def_id), + kind => bug!("unexpected associated item kind: {:?}", kind), } } +} +impl ItemFragment { /// Render the fragment, including the leading `#`. crate fn render(&self, s: &mut String, tcx: TyCtxt<'_>) -> std::fmt::Result { write!(s, "#")?; @@ -331,7 +289,7 @@ fn from_assoc_item(item: &ty::AssocItem) -> Self { FragmentKind::StructField => write!(s, "structfield.{}", name), FragmentKind::Variant => write!(s, "variant.{}", name), FragmentKind::VariantField => { - let variant = tcx.item_name(tcx.parent(def_id).unwrap()); + let variant = tcx.item_name(tcx.parent(def_id)); write!(s, "variant.{}.field.{}", variant, name) } } @@ -386,9 +344,9 @@ fn variant_field<'path>( path_str: &'path str, item_id: ItemId, module_id: DefId, - ) -> Result<(Res, Option), ErrorKind<'path>> { + ) -> Result<(Res, DefId), UnresolvedPath<'path>> { let tcx = self.cx.tcx; - let no_res = || ResolutionFailure::NotResolved { + let no_res = || UnresolvedPath { item_id, module_id, partial_res: None, @@ -415,42 +373,27 @@ fn variant_field<'path>( let ty_res = self.resolve_path(&path, TypeNS, item_id, module_id).ok_or_else(no_res)?; match ty_res { - Res::Def(DefKind::Enum, did) => { - if tcx - .inherent_impls(did) - .iter() - .flat_map(|imp| tcx.associated_items(*imp).in_definition_order()) - .any(|item| item.name == variant_name) - { - // This is just to let `fold_item` know that this shouldn't be considered; - // it's a bug for the error to make it to the user - return Err(ResolutionFailure::Dummy.into()); - } - match tcx.type_of(did).kind() { - ty::Adt(def, _) if def.is_enum() => { - if let Some(field) = def.all_fields().find(|f| f.name == variant_field_name) - { - Ok((ty_res, Some(ItemFragment(FragmentKind::VariantField, field.did)))) - } else { - Err(ResolutionFailure::NotResolved { - item_id, - module_id, - partial_res: Some(Res::Def(DefKind::Enum, def.did())), - unresolved: variant_field_name.to_string().into(), - } - .into()) - } + Res::Def(DefKind::Enum, did) => match tcx.type_of(did).kind() { + ty::Adt(def, _) if def.is_enum() => { + if let Some(field) = def.all_fields().find(|f| f.name == variant_field_name) { + Ok((ty_res, field.did)) + } else { + Err(UnresolvedPath { + item_id, + module_id, + partial_res: Some(Res::Def(DefKind::Enum, def.did())), + unresolved: variant_field_name.to_string().into(), + }) } - _ => unreachable!(), } - } - _ => Err(ResolutionFailure::NotResolved { + _ => unreachable!(), + }, + _ => Err(UnresolvedPath { item_id, module_id, partial_res: Some(ty_res), unresolved: variant_name.to_string().into(), - } - .into()), + }), } } @@ -460,35 +403,13 @@ fn resolve_primitive_associated_item( prim_ty: PrimitiveType, ns: Namespace, item_name: Symbol, - ) -> Option<(Res, ItemFragment)> { + ) -> Option<(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_) - .map(|item| { - let fragment = ItemFragment::from_assoc_item(item); - (Res::Primitive(prim_ty), fragment) - }) - }) - } - - /// Resolves a string as a macro. - /// - /// FIXME(jynelson): Can this be unified with `resolve()`? - fn resolve_macro( - &self, - path_str: &'a str, - item_id: ItemId, - module_id: DefId, - ) -> Result> { - self.resolve_path(path_str, MacroNS, item_id, module_id).ok_or_else(|| { - ResolutionFailure::NotResolved { - item_id, - module_id, - partial_res: None, - unresolved: path_str.into(), - } + .map(|item| (Res::Primitive(prim_ty), item.def_id)) }) } @@ -506,10 +427,10 @@ fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: ItemId) -> Opt | DefKind::AssocTy | DefKind::Variant | DefKind::Field) => { - let parent_def_id = tcx.parent(def_id).expect("nested item has no parent"); + let parent_def_id = tcx.parent(def_id); if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant { - tcx.parent(parent_def_id).expect("variant has no parent") + tcx.parent(parent_def_id) } else { parent_def_id } @@ -556,7 +477,17 @@ fn resolve_path( // Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`). let result = self .cx - .enter_resolver(|resolver| resolver.resolve_rustdoc_path(path_str, ns, module_id)) + .resolver_caches + .doc_link_resolutions + .get(&(Symbol::intern(path_str), ns, module_id)) + .copied() + .unwrap_or_else(|| { + self.cx.enter_resolver(|resolver| { + let parent_scope = + ParentScope::module(resolver.expect_module(module_id), resolver); + resolver.resolve_rustdoc_path(path_str, ns, parent_scope) + }) + }) .and_then(|res| res.try_into().ok()) .or_else(|| resolve_primitive(path_str, ns)) .or_else(|| self.resolve_macro_rules(path_str, ns)); @@ -572,43 +503,25 @@ fn resolve<'path>( ns: Namespace, item_id: ItemId, module_id: DefId, - user_fragment: &Option, - ) -> Result<(Res, Option), ErrorKind<'path>> { - let (res, rustdoc_fragment) = self.resolve_inner(path_str, ns, item_id, module_id)?; - let chosen_fragment = match (user_fragment, rustdoc_fragment) { - (Some(_), Some(r_frag)) => { - let diag_res = match r_frag { - ItemFragment(_, did) => Res::Def(self.cx.tcx.def_kind(did), did), - }; - let failure = AnchorFailure::RustdocAnchorConflict(diag_res); - return Err(ErrorKind::AnchorFailure(failure)); - } - (Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())), - (None, Some(r_frag)) => Some(UrlFragment::Item(r_frag)), - (None, None) => None, - }; - Ok((res, chosen_fragment)) - } - - fn resolve_inner<'path>( - &mut self, - path_str: &'path str, - ns: Namespace, - item_id: ItemId, - module_id: DefId, - ) -> Result<(Res, Option), ErrorKind<'path>> { + ) -> Result<(Res, Option), UnresolvedPath<'path>> { if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) { - match res { - // FIXME(#76467): make this fallthrough to lookup the associated - // item a separate function. - Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => assert_eq!(ns, ValueNS), - Res::Def(DefKind::AssocTy, _) => assert_eq!(ns, TypeNS), - Res::Def(DefKind::Variant, _) => { - return handle_variant(self.cx, res); + return Ok(match res { + Res::Def( + DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy | DefKind::Variant, + def_id, + ) => { + let parent_def_id = self.cx.tcx.parent(def_id).unwrap(); + (Res::from_def_id(self.cx.tcx, parent_def_id), Some(def_id)) } - // Not a trait item; just return what we found. - _ => return Ok((res, None)), - } + _ => ((res, None)), + }); + } else if ns == MacroNS { + return Err(UnresolvedPath { + item_id, + module_id, + partial_res: None, + unresolved: path_str.into(), + }); } // Try looking for methods and associated items. @@ -624,7 +537,7 @@ fn resolve_inner<'path>( // 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::NotResolved { + UnresolvedPath { item_id, module_id, partial_res: None, @@ -639,24 +552,21 @@ fn resolve_inner<'path>( resolve_primitive(&path_root, TypeNS) .or_else(|| self.resolve_path(&path_root, TypeNS, item_id, module_id)) .and_then(|ty_res| { - let (res, fragment) = - self.resolve_associated_item(ty_res, item_name, ns, module_id)?; - - Some(Ok((res, Some(fragment)))) + self.resolve_associated_item(ty_res, item_name, ns, module_id).map(Ok) }) .unwrap_or_else(|| { if ns == Namespace::ValueNS { self.variant_field(path_str, item_id, module_id) } else { - Err(ResolutionFailure::NotResolved { + Err(UnresolvedPath { item_id, module_id, partial_res: None, unresolved: path_root.into(), - } - .into()) + }) } }) + .map(|(res, def_id)| (res, Some(def_id))) } /// Convert a DefId to a Res, where possible. @@ -681,7 +591,7 @@ fn def_id_to_res(&self, ty_id: DefId) -> Option { ty::FnPtr(_) => Res::Primitive(Fn), ty::Never => Res::Primitive(Never), ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) | ty::Foreign(did) => { - Res::Def(self.cx.tcx.def_kind(did), did) + Res::from_def_id(self.cx.tcx, did) } ty::Projection(_) | ty::Closure(..) @@ -738,23 +648,18 @@ fn resolve_associated_item( item_name: Symbol, ns: Namespace, module_id: DefId, - ) -> Option<(Res, ItemFragment)> { + ) -> Option<(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 assoc_item = self - .primitive_type_to_ty(prim) + self.primitive_type_to_ty(prim) .map(|ty| { resolve_associated_trait_item(ty, module_id, item_name, ns, self.cx) }) - .flatten(); - - assoc_item.map(|item| { - let fragment = ItemFragment::from_assoc_item(&item); - (root_res, fragment) - }) + .flatten() + .map(|item| (root_res, item.def_id)) }) } Res::Def(DefKind::TyAlias, did) => { @@ -775,10 +680,7 @@ fn resolve_associated_item( ty::Adt(adt_def, _) => { for variant in adt_def.variants() { if variant.name == item_name { - return Some(( - root_res, - ItemFragment(FragmentKind::Variant, variant.def_id), - )); + return Some((root_res, variant.def_id)); } } } @@ -819,8 +721,7 @@ fn resolve_associated_item( debug!("got associated item {:?}", assoc_item); if let Some(item) = assoc_item { - let fragment = ItemFragment::from_assoc_item(&item); - return Some((root_res, fragment)); + return Some((root_res, item.def_id)); } if ns != Namespace::ValueNS { @@ -848,59 +749,22 @@ fn resolve_associated_item( }; let field = def.non_enum_variant().fields.iter().find(|item| item.name == item_name)?; - Some((root_res, ItemFragment(FragmentKind::StructField, field.did))) + Some((root_res, field.did)) } 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 fragment = ItemFragment::from_assoc_item(item); let res = Res::Def(item.kind.as_def_kind(), item.def_id); - (res, fragment) + (res, item.def_id) }), _ => None, } } +} - /// Used for reporting better errors. - /// - /// Returns whether the link resolved 'fully' in another namespace. - /// 'fully' here means that all parts of the link resolved, not just some path segments. - /// This returns the `Res` even if it was erroneous for some reason - /// (such as having invalid URL fragments or being in the wrong namespace). - fn check_full_res( - &mut self, - ns: Namespace, - path_str: &str, - item_id: ItemId, - module_id: DefId, - extra_fragment: &Option, - ) -> Option { - // resolve() can't be used for macro namespace - let result = match ns { - Namespace::MacroNS => self - .resolve_macro(path_str, item_id, module_id) - .map(|res| (res, None)) - .map_err(ErrorKind::from), - Namespace::TypeNS | Namespace::ValueNS => { - self.resolve(path_str, ns, item_id, module_id, extra_fragment) - } - }; - - let res = match result { - Ok((res, frag)) => { - if let Some(UrlFragment::Item(ItemFragment(_, id))) = frag { - Some(Res::Def(self.cx.tcx.def_kind(id), id)) - } else { - Some(res) - } - } - Err(ErrorKind::Resolve(box kind)) => kind.full_res(), - Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res))) => Some(res), - Err(ErrorKind::AnchorFailure(AnchorFailure::MultipleAnchors)) => None, - }; - res - } +fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { + assoc_item.map_or(base, |def_id| Res::from_def_id(tcx, def_id)) } /// Look to see if a resolved item has an associated item named `item_name`. @@ -1025,37 +889,50 @@ fn is_derive_trait_collision(ns: &PerNS DocVisitor for LinkCollector<'a, 'tcx> { fn visit_item(&mut self, item: &Item) { let parent_node = - item.def_id.as_def_id().and_then(|did| find_nearest_parent_module(self.cx.tcx, did)); + item.item_id.as_def_id().and_then(|did| find_nearest_parent_module(self.cx.tcx, did)); if parent_node.is_some() { - trace!("got parent node for {:?} {:?}, id {:?}", item.type_(), item.name, item.def_id); + trace!("got parent node for {:?} {:?}, id {:?}", item.type_(), item.name, item.item_id); } let inner_docs = item.inner_docs(self.cx.tcx); if item.is_mod() && inner_docs { - self.mod_ids.push(item.def_id.expect_def_id()); + self.mod_ids.push(item.item_id.expect_def_id()); } // We want to resolve in the lexical scope of the documentation. // In the presence of re-exports, this is not the same as the module of the item. // Rather than merging all documentation into one, resolve it one attribute at a time // so we know which module it came from. - for (parent_module, doc) in item.attrs.collapsed_doc_value_by_module_level() { + for (parent_module, doc) in item.attrs.prepare_to_doc_link_resolution() { + if !may_have_doc_links(&doc) { + continue; + } debug!("combined_docs={}", doc); // NOTE: if there are links that start in one crate and end in another, this will not resolve them. // This is a degenerate case and it's not supported by rustdoc. let parent_node = parent_module.or(parent_node); - for md_link in markdown_links(&doc) { + let mut tmp_links = self + .cx + .resolver_caches + .markdown_links + .take() + .expect("`markdown_links` are already borrowed"); + if !tmp_links.contains_key(&doc) { + tmp_links.insert(doc.clone(), preprocessed_markdown_links(&doc)); + } + for md_link in &tmp_links[&doc] { let link = self.resolve_link(&item, &doc, parent_node, md_link); if let Some(link) = link { - self.cx.cache.intra_doc_links.entry(item.def_id).or_default().push(link); + self.cx.cache.intra_doc_links.entry(item.item_id).or_default().push(link); } } + self.cx.resolver_caches.markdown_links = Some(tmp_links); } if item.is_mod() { if !inner_docs { - self.mod_ids.push(item.def_id.expect_def_id()); + self.mod_ids.push(item.item_id.expect_def_id()); } self.visit_item_recur(item); @@ -1066,18 +943,28 @@ fn visit_item(&mut self, item: &Item) { } } -enum PreprocessingError<'a> { - Anchor(AnchorFailure), +enum PreprocessingError { + /// User error: `[std#x#y]` is not valid + MultipleAnchors, Disambiguator(Range, String), - Resolution(ResolutionFailure<'a>, String, Option), + MalformedGenerics(MalformedGenerics, String), } -impl From for PreprocessingError<'_> { - fn from(err: AnchorFailure) -> Self { - Self::Anchor(err) +impl PreprocessingError { + fn report(&self, cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) { + match self { + PreprocessingError::MultipleAnchors => report_multiple_anchors(cx, diag_info), + PreprocessingError::Disambiguator(range, msg) => { + disambiguator_error(cx, diag_info, range.clone(), msg) + } + PreprocessingError::MalformedGenerics(err, path_str) => { + report_malformed_generics(cx, diag_info, *err, path_str) + } + } } } +#[derive(Clone)] struct PreprocessingInfo { path_str: String, disambiguator: Option, @@ -1085,15 +972,18 @@ struct PreprocessingInfo { link_text: String, } +// Not a typedef to avoid leaking several private structures from this module. +crate struct PreprocessedMarkdownLink(Result, MarkdownLink); + /// Returns: /// - `None` if the link should be ignored. /// - `Some(Err)` if the link should emit an error /// - `Some(Ok)` if the link is valid /// /// `link_buffer` is needed for lifetime reasons; it will always be overwritten and the contents ignored. -fn preprocess_link<'a>( - ori_link: &'a MarkdownLink, -) -> Option>> { +fn preprocess_link( + ori_link: &MarkdownLink, +) -> Option> { // [] is mostly likely not supposed to be a link if ori_link.link.is_empty() { return None; @@ -1115,7 +1005,7 @@ fn preprocess_link<'a>( let extra_fragment = parts.next(); if parts.next().is_some() { // A valid link can't have multiple #'s - return Some(Err(AnchorFailure::MultipleAnchors.into())); + return Some(Err(PreprocessingError::MultipleAnchors)); } // Parse and strip the disambiguator from the link, if present. @@ -1143,13 +1033,9 @@ fn preprocess_link<'a>( let path_str = if path_str.contains(['<', '>'].as_slice()) { match strip_generics_from_path(path_str) { Ok(path) => path, - Err(err_kind) => { + Err(err) => { debug!("link has malformed generics: {}", path_str); - return Some(Err(PreprocessingError::Resolution( - err_kind, - path_str.to_owned(), - disambiguator, - ))); + return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned()))); } } } else { @@ -1172,6 +1058,12 @@ fn preprocess_link<'a>( })) } +fn preprocessed_markdown_links(s: &str) -> Vec { + markdown_links(s, |link| { + preprocess_link(&link).map(|pp_link| PreprocessedMarkdownLink(pp_link, link)) + }) +} + impl LinkCollector<'_, '_> { /// This is the entry point for resolving an intra-doc link. /// @@ -1181,8 +1073,9 @@ fn resolve_link( item: &Item, dox: &str, parent_node: Option, - ori_link: MarkdownLink, + link: &PreprocessedMarkdownLink, ) -> Option { + let PreprocessedMarkdownLink(pp_link, ori_link) = link; trace!("considering link '{}'", ori_link.link); let diag_info = DiagnosticInfo { @@ -1192,30 +1085,9 @@ fn resolve_link( link_range: ori_link.range.clone(), }; - let PreprocessingInfo { ref path_str, disambiguator, extra_fragment, link_text } = - match preprocess_link(&ori_link)? { - Ok(x) => x, - Err(err) => { - match err { - PreprocessingError::Anchor(err) => anchor_failure(self.cx, diag_info, err), - PreprocessingError::Disambiguator(range, msg) => { - disambiguator_error(self.cx, diag_info, range, &msg) - } - PreprocessingError::Resolution(err, path_str, disambiguator) => { - resolution_failure( - self, - diag_info, - &path_str, - disambiguator, - smallvec![err], - ); - } - } - return None; - } - }; - - let inner_docs = item.inner_docs(self.cx.tcx); + let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } = + pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?; + let disambiguator = *disambiguator; // In order to correctly resolve intra-doc links we need to // pick a base AST node to work from. If the documentation for @@ -1228,29 +1100,18 @@ fn resolve_link( // we've already pushed this node onto the resolution stack but // for outer comments we explicitly try and resolve against the // parent_node first. + let inner_docs = item.inner_docs(self.cx.tcx); let base_node = if item.is_mod() && inner_docs { self.mod_ids.last().copied() } else { parent_node }; - - let Some(module_id) = base_node else { - // This is a bug. - debug!("attempting to resolve item without parent module: {}", path_str); - resolution_failure( - self, - diag_info, - path_str, - disambiguator, - smallvec![ResolutionFailure::NoParentItem], - ); - return None; - }; + let module_id = base_node.expect("doc link without parent module"); let (mut res, fragment) = self.resolve_with_disambiguator_cached( ResolutionInfo { - item_id: item.def_id, + item_id: item.item_id, module_id, dis: disambiguator, path_str: path_str.to_owned(), - extra_fragment, + extra_fragment: extra_fragment.clone(), }, diag_info.clone(), // this struct should really be Copy, but Range is not :( matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut), @@ -1302,7 +1163,7 @@ fn resolve_link( // 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.def_id.is_local() + && item.item_id.is_local() && !self.cx.tcx.features().intra_doc_pointers { self.report_rawptr_assoc_feature_gate(dox, &ori_link, item); @@ -1320,8 +1181,8 @@ fn resolve_link( } Some(ItemLink { - link: ori_link.link, - link_text, + link: ori_link.link.clone(), + link_text: link_text.clone(), did: res.def_id(self.cx.tcx), fragment, }) @@ -1343,7 +1204,12 @@ fn resolve_link( &diag_info, )?; let id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id)); - Some(ItemLink { link: ori_link.link, link_text, did: id, fragment }) + Some(ItemLink { + link: ori_link.link.clone(), + link_text: link_text.clone(), + did: id, + fragment, + }) } } } @@ -1386,7 +1252,7 @@ fn verify_disambiguator( // The `expect_def_id()` should be okay because `local_def_id_to_hir_id` // would presumably panic if a fake `DefIndex` were passed. .and_then(|dst_id| { - item.def_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id)) + item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id)) }) { if self.cx.tcx.privacy_access_levels(()).is_exported(src_id) @@ -1461,7 +1327,21 @@ fn resolve_with_disambiguator_cached( } } - let res = self.resolve_with_disambiguator(&key, diag); + let res = self.resolve_with_disambiguator(&key, diag.clone()).and_then(|(res, def_id)| { + let fragment = match (&key.extra_fragment, def_id) { + (Some(_), Some(def_id)) => { + report_anchor_conflict(self.cx, diag, Res::from_def_id(self.cx.tcx, def_id)); + return None; + } + (Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())), + (None, Some(def_id)) => Some(UrlFragment::Item(ItemFragment( + FragmentKind::from_def_id(self.cx.tcx, def_id), + def_id, + ))), + (None, None) => None, + }; + Some((res, fragment)) + }); // Cache only if resolved successfully - don't silence duplicate errors if let Some(res) = res { @@ -1486,103 +1366,59 @@ fn resolve_with_disambiguator( &mut self, key: &ResolutionInfo, diag: DiagnosticInfo<'_>, - ) -> Option<(Res, Option)> { + ) -> Option<(Res, Option)> { let disambiguator = key.dis; let path_str = &key.path_str; let item_id = key.item_id; let base_node = key.module_id; - let extra_fragment = &key.extra_fragment; match disambiguator.map(Disambiguator::ns) { - Some(expected_ns @ (ValueNS | TypeNS)) => { - match self.resolve(path_str, expected_ns, item_id, base_node, extra_fragment) { + Some(expected_ns) => { + match self.resolve(path_str, expected_ns, item_id, base_node) { Ok(res) => Some(res), - Err(ErrorKind::Resolve(box mut kind)) => { + Err(err) => { // We only looked in one namespace. Try to give a better error if possible. - if kind.full_res().is_none() { - let other_ns = if expected_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, - path_str, - item_id, - base_node, - extra_fragment, - ) { - kind = ResolutionFailure::WrongNamespace { res, expected_ns }; + // 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. + let mut err = ResolutionFailure::NotResolved(err); + for other_ns in [TypeNS, ValueNS, MacroNS] { + if other_ns != expected_ns { + if let Ok(res) = + self.resolve(path_str, other_ns, item_id, base_node) + { + err = ResolutionFailure::WrongNamespace { + res: full_res(self.cx.tcx, res), + expected_ns, + }; break; } } } - resolution_failure(self, diag, path_str, disambiguator, smallvec![kind]); + resolution_failure(self, diag, path_str, disambiguator, smallvec![err]); // 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. None } - Err(ErrorKind::AnchorFailure(msg)) => { - anchor_failure(self.cx, diag, msg); - None - } } } None => { // Try everything! - let mut candidates = PerNS { - macro_ns: self - .resolve_macro(path_str, item_id, base_node) - .map(|res| (res, extra_fragment.clone().map(UrlFragment::UserWritten))), - type_ns: match self.resolve( - path_str, - TypeNS, - item_id, - base_node, - extra_fragment, - ) { - Ok(res) => { - debug!("got res in TypeNS: {:?}", res); - Ok(res) - } - Err(ErrorKind::AnchorFailure(msg)) => { - anchor_failure(self.cx, diag, msg); - return None; - } - Err(ErrorKind::Resolve(box kind)) => Err(kind), - }, - value_ns: match self.resolve( - path_str, - ValueNS, - item_id, - base_node, - extra_fragment, - ) { - Ok(res) => Ok(res), - Err(ErrorKind::AnchorFailure(msg)) => { - anchor_failure(self.cx, diag, msg); - return None; - } - Err(ErrorKind::Resolve(box kind)) => Err(kind), - } - .and_then(|(res, fragment)| { - // Constructors are picked up in the type namespace. + let mut candidate = |ns| { + self.resolve(path_str, ns, item_id, base_node) + .map_err(ResolutionFailure::NotResolved) + }; + + 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 }) } - _ => { - match (fragment, extra_fragment.clone()) { - (Some(fragment), Some(_)) => { - // Shouldn't happen but who knows? - Ok((res, Some(fragment))) - } - (fragment, None) => Ok((res, fragment)), - (None, fragment) => { - Ok((res, fragment.map(UrlFragment::UserWritten))) - } - } - } + _ => Ok((res, def_id)), } }), }; @@ -1606,38 +1442,17 @@ fn resolve_with_disambiguator( } else if len == 2 && is_derive_trait_collision(&candidates) { Some(candidates.type_ns.unwrap()) } else { - if is_derive_trait_collision(&candidates) { - candidates.macro_ns = Err(ResolutionFailure::Dummy); - } + let ignore_macro = is_derive_trait_collision(&candidates); // If we're reporting an ambiguity, don't mention the namespaces that failed - let candidates = candidates.map(|candidate| candidate.ok().map(|(res, _)| res)); + 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 } } - Some(MacroNS) => { - match self.resolve_macro(path_str, item_id, base_node) { - Ok(res) => Some((res, extra_fragment.clone().map(UrlFragment::UserWritten))), - Err(mut kind) => { - // `resolve_macro` only looks in the macro namespace. Try to give a better error if possible. - for ns in [TypeNS, ValueNS] { - if let Some(res) = self.check_full_res( - ns, - path_str, - item_id, - base_node, - extra_fragment, - ) { - kind = - ResolutionFailure::WrongNamespace { res, expected_ns: MacroNS }; - break; - } - } - resolution_failure(self, diag, path_str, disambiguator, smallvec![kind]); - None - } - } - } } } } @@ -1864,7 +1679,7 @@ fn report_diagnostic( DiagnosticInfo { item, ori_link: _, dox, link_range }: &DiagnosticInfo<'_>, decorate: impl FnOnce(&mut Diagnostic, Option), ) { - let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.def_id) + let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else { // If non-local, no need to check anything. info!("ignoring warning from parent crate: {}", msg); @@ -1952,12 +1767,12 @@ fn resolution_failure( } variants_seen.push(variant); - if let ResolutionFailure::NotResolved { + if let ResolutionFailure::NotResolved(UnresolvedPath { item_id, module_id, partial_res, unresolved, - } = &mut failure + }) = &mut failure { use DefKind::*; @@ -1983,11 +1798,9 @@ fn split(path: &str) -> Option<(&str, &str)> { }; name = start; for ns in [TypeNS, ValueNS, MacroNS] { - if let Some(res) = - collector.check_full_res(ns, start, item_id, module_id, &None) - { + if let Ok(res) = collector.resolve(start, ns, item_id, module_id) { debug!("found partial_res={:?}", res); - *partial_res = Some(res); + *partial_res = Some(full_res(collector.cx.tcx, res)); *unresolved = end.into(); break 'outer; } @@ -2087,7 +1900,6 @@ fn split(path: &str) -> Option<(&str, &str)> { } let note = match failure { ResolutionFailure::NotResolved { .. } => unreachable!("handled above"), - ResolutionFailure::Dummy => continue, ResolutionFailure::WrongNamespace { res, expected_ns } => { suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp); @@ -2097,38 +1909,6 @@ fn split(path: &str) -> Option<(&str, &str)> { expected_ns.descr() ) } - ResolutionFailure::NoParentItem => { - // FIXME(eddyb) this doesn't belong here, whatever made - // the `ResolutionFailure::NoParentItem` should emit an - // immediate or delayed `span_bug` about the issue. - tcx.sess.delay_span_bug( - sp.unwrap_or(DUMMY_SP), - "intra-doc link missing parent item", - ); - - "BUG: all intra-doc links should have a parent item".to_owned() - } - ResolutionFailure::MalformedGenerics(variant) => match variant { - MalformedGenerics::UnbalancedAngleBrackets => { - String::from("unbalanced angle brackets") - } - MalformedGenerics::MissingType => { - String::from("missing type for generic parameters") - } - MalformedGenerics::HasFullyQualifiedSyntax => { - diag.note("see https://github.com/rust-lang/rust/issues/74563 for more information"); - String::from("fully-qualified syntax is unsupported") - } - MalformedGenerics::InvalidPathSeparator => { - String::from("has invalid path separator") - } - MalformedGenerics::TooManyAngleBrackets => { - String::from("too many angle brackets") - } - MalformedGenerics::EmptyAngleBrackets => { - String::from("empty angle brackets") - } - }, }; if let Some(span) = sp { diag.span_label(span, ¬e); @@ -2140,22 +1920,24 @@ fn split(path: &str) -> Option<(&str, &str)> { ); } -/// Report an anchor failure. -fn anchor_failure(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, failure: AnchorFailure) { - let (msg, anchor_idx) = match failure { - AnchorFailure::MultipleAnchors => { - (format!("`{}` contains multiple anchors", diag_info.ori_link), 1) - } - AnchorFailure::RustdocAnchorConflict(res) => ( - format!( - "`{}` contains an anchor, but links to {kind}s are already anchored", - diag_info.ori_link, - kind = res.descr(), - ), - 0, - ), - }; +fn report_multiple_anchors(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) { + let msg = format!("`{}` contains multiple anchors", diag_info.ori_link); + anchor_failure(cx, diag_info, &msg, 1) +} +fn report_anchor_conflict(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, res: Res) { + let (link, kind) = (diag_info.ori_link, res.descr()); + let msg = format!("`{link}` contains an anchor, but links to {kind}s are already anchored"); + anchor_failure(cx, diag_info, &msg, 0) +} + +/// Report an anchor failure. +fn anchor_failure( + cx: &DocContext<'_>, + diag_info: DiagnosticInfo<'_>, + msg: &str, + anchor_idx: usize, +) { report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, |diag, sp| { if let Some(mut sp) = sp { if let Some((fragment_offset, _)) = @@ -2165,13 +1947,6 @@ fn anchor_failure(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, failure: A } diag.span_label(sp, "invalid anchor"); } - if let AnchorFailure::RustdocAnchorConflict(Res::Primitive(_)) = failure { - if let Some(sp) = sp { - span_bug!(sp, "anchors should be allowed now"); - } else { - bug!("anchors should be allowed now"); - } - } }); } @@ -2192,6 +1967,40 @@ fn disambiguator_error( }); } +fn report_malformed_generics( + cx: &DocContext<'_>, + diag_info: DiagnosticInfo<'_>, + err: MalformedGenerics, + path_str: &str, +) { + report_diagnostic( + cx.tcx, + BROKEN_INTRA_DOC_LINKS, + &format!("unresolved link to `{}`", path_str), + &diag_info, + |diag, sp| { + let note = match err { + MalformedGenerics::UnbalancedAngleBrackets => "unbalanced angle brackets", + MalformedGenerics::MissingType => "missing type for generic parameters", + MalformedGenerics::HasFullyQualifiedSyntax => { + diag.note( + "see https://github.com/rust-lang/rust/issues/74563 for more information", + ); + "fully-qualified syntax is unsupported" + } + MalformedGenerics::InvalidPathSeparator => "has invalid path separator", + MalformedGenerics::TooManyAngleBrackets => "too many angle brackets", + MalformedGenerics::EmptyAngleBrackets => "empty angle brackets", + }; + if let Some(span) = sp { + diag.span_label(span, note); + } else { + diag.note(note); + } + }, + ); +} + /// Report an ambiguity error, where there were multiple possible resolutions. fn ambiguity_error( cx: &DocContext<'_>, @@ -2288,21 +2097,6 @@ fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str: }); } -/// Given an enum variant's res, return the res of its enum and the associated fragment. -fn handle_variant( - cx: &DocContext<'_>, - res: Res, -) -> Result<(Res, Option), ErrorKind<'static>> { - cx.tcx - .parent(res.def_id(cx.tcx)) - .map(|parent| { - let parent_def = Res::Def(DefKind::Enum, parent); - let variant = cx.tcx.expect_variant_res(res.as_hir_res().unwrap()); - (parent_def, Some(ItemFragment(FragmentKind::Variant, variant.def_id))) - }) - .ok_or_else(|| ResolutionFailure::NoParentItem.into()) -} - /// Resolve a primitive type or value. fn resolve_primitive(path_str: &str, ns: Namespace) -> Option { if ns != TypeNS { @@ -2342,7 +2136,7 @@ fn resolve_primitive(path_str: &str, ns: Namespace) -> Option { Some(Res::Primitive(prim)) } -fn strip_generics_from_path(path_str: &str) -> Result> { +fn strip_generics_from_path(path_str: &str) -> Result { let mut stripped_segments = vec![]; let mut path = path_str.chars().peekable(); let mut segment = Vec::new(); @@ -2357,9 +2151,7 @@ fn strip_generics_from_path(path_str: &str) -> Result { @@ -2367,14 +2159,10 @@ fn strip_generics_from_path(path_str: &str) -> Result { - return Err(ResolutionFailure::MalformedGenerics( - MalformedGenerics::TooManyAngleBrackets, - )); + return Err(MalformedGenerics::TooManyAngleBrackets); } Some('>') => { - return Err(ResolutionFailure::MalformedGenerics( - MalformedGenerics::EmptyAngleBrackets, - )); + return Err(MalformedGenerics::EmptyAngleBrackets); } Some(chr) => { segment.push(chr); @@ -2402,16 +2190,10 @@ fn strip_generics_from_path(path_str: &str) -> Result, -) -> Result> { +fn strip_generics_from_path_segment(segment: Vec) -> Result { let mut stripped_segment = String::new(); let mut param_depth = 0; @@ -2426,9 +2208,7 @@ fn strip_generics_from_path_segment( if latest_generics_chunk.contains(" as ") { // The segment tries to use fully-qualified syntax, which is currently unsupported. // Give a helpful error message instead of completely ignoring the angle brackets. - return Err(ResolutionFailure::MalformedGenerics( - MalformedGenerics::HasFullyQualifiedSyntax, - )); + return Err(MalformedGenerics::HasFullyQualifiedSyntax); } } else { if param_depth == 0 { @@ -2443,6 +2223,6 @@ fn strip_generics_from_path_segment( Ok(stripped_segment) } else { // The segment has unbalanced angle brackets, e.g. `Vec>` - Err(ResolutionFailure::MalformedGenerics(MalformedGenerics::UnbalancedAngleBrackets)) + Err(MalformedGenerics::UnbalancedAngleBrackets) } }