From b62b82aef4b6aa667161c664fc3ae738c2fc6b9f Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Tue, 1 Feb 2022 20:30:32 +0800 Subject: [PATCH] Resolve documentation links in rustc and store the results in metadata This commit implements MCP https://github.com/rust-lang/compiler-team/issues/584 It also removes code that is no longer used, and that includes code cloning resolver, so issue #83761 is fixed. --- Cargo.lock | 2 +- .../src/stable_hasher.rs | 8 + compiler/rustc_hir/src/def.rs | 16 +- compiler/rustc_metadata/src/rmeta/decoder.rs | 41 +- .../src/rmeta/decoder/cstore_impl.rs | 34 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 37 +- compiler/rustc_metadata/src/rmeta/mod.rs | 7 +- compiler/rustc_middle/src/arena.rs | 1 + compiler/rustc_middle/src/query/mod.rs | 12 + compiler/rustc_middle/src/ty/mod.rs | 4 +- compiler/rustc_middle/src/ty/parameterized.rs | 2 + compiler/rustc_middle/src/ty/query.rs | 2 +- compiler/rustc_resolve/Cargo.toml | 1 + .../rustc_resolve/src/build_reduced_graph.rs | 4 +- compiler/rustc_resolve/src/late.rs | 128 +++++- compiler/rustc_resolve/src/lib.rs | 38 +- compiler/rustc_resolve/src/macros.rs | 2 +- compiler/rustc_resolve/src/rustdoc.rs | 361 ++++++++++++++++ src/librustdoc/Cargo.toml | 1 - src/librustdoc/clean/types.rs | 171 +------- src/librustdoc/clean/types/tests.rs | 1 + src/librustdoc/core.rs | 24 +- src/librustdoc/html/markdown.rs | 10 +- src/librustdoc/lib.rs | 17 +- .../passes/collect_intra_doc_links.rs | 173 +------- .../passes/collect_intra_doc_links/early.rs | 404 +----------------- src/librustdoc/passes/mod.rs | 3 +- src/tools/tidy/src/deps.rs | 2 + 28 files changed, 653 insertions(+), 853 deletions(-) create mode 100644 compiler/rustc_resolve/src/rustdoc.rs diff --git a/Cargo.lock b/Cargo.lock index ad01ef5e41f16..ed5065d7611a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4613,6 +4613,7 @@ name = "rustc_resolve" version = "0.0.0" dependencies = [ "bitflags", + "pulldown-cmark 0.9.2", "rustc_arena", "rustc_ast", "rustc_ast_pretty", @@ -4878,7 +4879,6 @@ dependencies = [ "itertools", "minifier", "once_cell", - "pulldown-cmark 0.9.2", "rayon", "regex", "rustdoc-json-types", diff --git a/compiler/rustc_data_structures/src/stable_hasher.rs b/compiler/rustc_data_structures/src/stable_hasher.rs index ae4836645fa41..e0d77cdaebb36 100644 --- a/compiler/rustc_data_structures/src/stable_hasher.rs +++ b/compiler/rustc_data_structures/src/stable_hasher.rs @@ -486,6 +486,14 @@ impl ToStableHashKey for String { } } +impl, T2: ToStableHashKey> ToStableHashKey for (T1, T2) { + type KeyType = (T1::KeyType, T2::KeyType); + #[inline] + fn to_stable_hash_key(&self, hcx: &HCX) -> Self::KeyType { + (self.0.to_stable_hash_key(hcx), self.1.to_stable_hash_key(hcx)) + } +} + impl HashStable for bool { #[inline] fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) { diff --git a/compiler/rustc_hir/src/def.rs b/compiler/rustc_hir/src/def.rs index cca5ead0f8395..f1801a0f844f7 100644 --- a/compiler/rustc_hir/src/def.rs +++ b/compiler/rustc_hir/src/def.rs @@ -2,6 +2,8 @@ use crate::hir; use rustc_ast as ast; use rustc_ast::NodeId; +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::stable_hasher::ToStableHashKey; use rustc_macros::HashStable_Generic; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::hygiene::MacroKind; @@ -472,7 +474,8 @@ impl PartialRes { /// Different kinds of symbols can coexist even if they share the same textual name. /// Therefore, they each have a separate universe (known as a "namespace"). -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encodable, Decodable)] +#[derive(HashStable_Generic)] pub enum Namespace { /// The type namespace includes `struct`s, `enum`s, `union`s, `trait`s, and `mod`s /// (and, by extension, crates). @@ -499,6 +502,15 @@ impl Namespace { } } +impl ToStableHashKey for Namespace { + type KeyType = Namespace; + + #[inline] + fn to_stable_hash_key(&self, _: &CTX) -> Namespace { + *self + } +} + /// Just a helper ‒ separate structure for each namespace. #[derive(Copy, Clone, Default, Debug)] pub struct PerNS { @@ -760,3 +772,5 @@ pub enum LifetimeRes { /// HACK: This is used to recover the NodeId of an elided lifetime. ElidedAnchor { start: NodeId, end: NodeId }, } + +pub type DocLinkResMap = FxHashMap<(Symbol, Namespace), Option>>; diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index e2b07fad6e782..800f85063c41a 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -11,7 +11,7 @@ use rustc_data_structures::sync::{Lock, LockGuard, Lrc, OnceCell}; use rustc_data_structures::unhash::UnhashMap; use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind}; use rustc_expand::proc_macro::{AttrProcMacro, BangProcMacro, DeriveProcMacro}; -use rustc_hir::def::{CtorKind, DefKind, Res}; +use rustc_hir::def::{CtorKind, DefKind, DocLinkResMap, Res}; use rustc_hir::def_id::{CrateNum, DefId, DefIndex, CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc_hir::definitions::{DefKey, DefPath, DefPathData, DefPathHash}; use rustc_hir::diagnostic_items::DiagnosticItems; @@ -1163,20 +1163,6 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { ) } - /// Decodes all inherent impls in the crate (for rustdoc). - fn get_inherent_impls(self) -> impl Iterator + 'a { - (0..self.root.tables.inherent_impls.size()).flat_map(move |i| { - let ty_index = DefIndex::from_usize(i); - let ty_def_id = self.local_def_id(ty_index); - self.root - .tables - .inherent_impls - .get(self, ty_index) - .decode(self) - .map(move |impl_index| (ty_def_id, self.local_def_id(impl_index))) - }) - } - /// Decodes all traits in the crate (for rustdoc and rustc diagnostics). fn get_traits(self) -> impl Iterator + 'a { self.root.traits.decode(self).map(move |index| self.local_def_id(index)) @@ -1195,13 +1181,6 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { }) } - fn get_all_incoherent_impls(self) -> impl Iterator + 'a { - self.cdata - .incoherent_impls - .values() - .flat_map(move |impls| impls.decode(self).map(move |idx| self.local_def_id(idx))) - } - fn get_incoherent_impls(self, tcx: TyCtxt<'tcx>, simp: SimplifiedType) -> &'tcx [DefId] { if let Some(impls) = self.cdata.incoherent_impls.get(&simp) { tcx.arena.alloc_from_iter(impls.decode(self).map(|idx| self.local_def_id(idx))) @@ -1598,6 +1577,24 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { fn get_is_intrinsic(self, index: DefIndex) -> bool { self.root.tables.is_intrinsic.get(self, index) } + + fn get_doc_link_resolutions(self, index: DefIndex) -> DocLinkResMap { + self.root + .tables + .doc_link_resolutions + .get(self, index) + .expect("no resolutions for a doc link") + .decode(self) + } + + fn get_doc_link_traits_in_scope(self, index: DefIndex) -> impl Iterator + 'a { + self.root + .tables + .doc_link_traits_in_scope + .get(self, index) + .expect("no traits in scope for a doc link") + .decode(self) + } } impl CrateMetadata { diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 07cc84ab95368..b12f9b5c917e8 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -345,6 +345,10 @@ provide! { tcx, def_id, other, cdata, expn_that_defined => { cdata.get_expn_that_defined(def_id.index, tcx.sess) } generator_diagnostic_data => { cdata.get_generator_diagnostic_data(tcx, def_id.index) } is_doc_hidden => { cdata.get_attr_flags(def_id.index).contains(AttrFlags::IS_DOC_HIDDEN) } + doc_link_resolutions => { tcx.arena.alloc(cdata.get_doc_link_resolutions(def_id.index)) } + doc_link_traits_in_scope => { + tcx.arena.alloc_from_iter(cdata.get_doc_link_traits_in_scope(def_id.index)) + } } pub(in crate::rmeta) fn provide(providers: &mut Providers) { @@ -613,36 +617,6 @@ impl CStore { self.get_crate_data(cnum).get_trait_impls() } - /// Decodes all inherent impls in the crate (for rustdoc). - pub fn inherent_impls_in_crate_untracked( - &self, - cnum: CrateNum, - ) -> impl Iterator + '_ { - self.get_crate_data(cnum).get_inherent_impls() - } - - /// Decodes all incoherent inherent impls in the crate (for rustdoc). - pub fn incoherent_impls_in_crate_untracked( - &self, - cnum: CrateNum, - ) -> impl Iterator + '_ { - self.get_crate_data(cnum).get_all_incoherent_impls() - } - - pub fn associated_item_def_ids_untracked<'a>( - &'a self, - def_id: DefId, - sess: &'a Session, - ) -> impl Iterator + 'a { - self.get_crate_data(def_id.krate).get_associated_item_def_ids(def_id.index, sess) - } - - pub fn may_have_doc_links_untracked(&self, def_id: DefId) -> bool { - self.get_crate_data(def_id.krate) - .get_attr_flags(def_id.index) - .contains(AttrFlags::MAY_HAVE_DOC_LINKS) - } - pub fn is_doc_hidden_untracked(&self, def_id: DefId) -> bool { self.get_crate_data(def_id.krate) .get_attr_flags(def_id.index) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 85e9ae9a98302..263c71ae70286 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -3,7 +3,6 @@ use crate::rmeta::def_path_hash_map::DefPathHashMapRef; use crate::rmeta::table::TableBuilder; use crate::rmeta::*; -use rustc_ast::util::comments; use rustc_ast::Attribute; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; @@ -772,7 +771,6 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { struct AnalyzeAttrState { is_exported: bool, - may_have_doc_links: bool, is_doc_hidden: bool, } @@ -790,15 +788,12 @@ fn analyze_attr(attr: &Attribute, state: &mut AnalyzeAttrState) -> bool { let mut should_encode = false; if rustc_feature::is_builtin_only_local(attr.name_or_empty()) { // Attributes marked local-only don't need to be encoded for downstream crates. - } else if let Some(s) = attr.doc_str() { + } else if attr.doc_str().is_some() { // We keep all doc comments reachable to rustdoc because they might be "imported" into // downstream crates if they use `#[doc(inline)]` to copy an item's documentation into // their own. if state.is_exported { should_encode = true; - if comments::may_have_doc_links(s.as_str()) { - state.may_have_doc_links = true; - } } } else if attr.has_name(sym::doc) { // If this is a `doc` attribute that doesn't have anything except maybe `inline` (as in @@ -1139,7 +1134,6 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let tcx = self.tcx; let mut state = AnalyzeAttrState { is_exported: tcx.effective_visibilities(()).is_exported(def_id), - may_have_doc_links: false, is_doc_hidden: false, }; let attr_iter = tcx @@ -1151,9 +1145,6 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record_array!(self.tables.attributes[def_id.to_def_id()] <- attr_iter); let mut attr_flags = AttrFlags::empty(); - if state.may_have_doc_links { - attr_flags |= AttrFlags::MAY_HAVE_DOC_LINKS; - } if state.is_doc_hidden { attr_flags |= AttrFlags::IS_DOC_HIDDEN; } @@ -1231,6 +1222,14 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { def_id.index })); } + + for (def_id, res_map) in &tcx.resolutions(()).doc_link_resolutions { + record!(self.tables.doc_link_resolutions[def_id.to_def_id()] <- res_map); + } + + for (def_id, traits) in &tcx.resolutions(()).doc_link_traits_in_scope { + record_array!(self.tables.doc_link_traits_in_scope[def_id.to_def_id()] <- traits); + } } #[instrument(level = "trace", skip(self))] @@ -1715,6 +1714,12 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record!(self.tables.lookup_stability[LOCAL_CRATE.as_def_id()] <- stability); } self.encode_deprecation(LOCAL_CRATE.as_def_id()); + if let Some(res_map) = tcx.resolutions(()).doc_link_resolutions.get(&CRATE_DEF_ID) { + record!(self.tables.doc_link_resolutions[LOCAL_CRATE.as_def_id()] <- res_map); + } + if let Some(traits) = tcx.resolutions(()).doc_link_traits_in_scope.get(&CRATE_DEF_ID) { + record_array!(self.tables.doc_link_traits_in_scope[LOCAL_CRATE.as_def_id()] <- traits); + } // Normally, this information is encoded when we walk the items // defined in this crate. However, we skip doing that for proc-macro crates, @@ -2225,6 +2230,18 @@ fn encode_metadata_impl(tcx: TyCtxt<'_>, path: &Path) { pub fn provide(providers: &mut Providers) { *providers = Providers { + doc_link_resolutions: |tcx, def_id| { + tcx.resolutions(()) + .doc_link_resolutions + .get(&def_id.expect_local()) + .expect("no resolutions for a doc link") + }, + doc_link_traits_in_scope: |tcx, def_id| { + tcx.resolutions(()) + .doc_link_traits_in_scope + .get(&def_id.expect_local()) + .expect("no traits in scope for a doc link") + }, traits_in_crate: |tcx, cnum| { assert_eq!(cnum, LOCAL_CRATE); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index a74aa381d9eb8..9227609cc8b66 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -9,7 +9,7 @@ use rustc_attr as attr; use rustc_data_structures::svh::Svh; use rustc_data_structures::sync::MetadataRef; use rustc_hir as hir; -use rustc_hir::def::{CtorKind, DefKind}; +use rustc_hir::def::{CtorKind, DefKind, DocLinkResMap}; use rustc_hir::def_id::{CrateNum, DefId, DefIndex, DefPathHash, StableCrateId}; use rustc_hir::definitions::DefKey; use rustc_hir::lang_items::LangItem; @@ -413,6 +413,8 @@ define_tables! { module_reexports: Table>, deduced_param_attrs: Table>, trait_impl_trait_tys: Table>>>, + doc_link_resolutions: Table>, + doc_link_traits_in_scope: Table>, } #[derive(TyEncodable, TyDecodable)] @@ -426,8 +428,7 @@ struct VariantData { bitflags::bitflags! { #[derive(Default)] pub struct AttrFlags: u8 { - const MAY_HAVE_DOC_LINKS = 1 << 0; - const IS_DOC_HIDDEN = 1 << 1; + const IS_DOC_HIDDEN = 1 << 0; } } diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index 2ba7ec5b15192..9d2144c443b4a 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -113,6 +113,7 @@ macro_rules! arena_types { [decode] trait_impl_trait_tys: rustc_data_structures::fx::FxHashMap>, [] bit_set_u32: rustc_index::bit_set::BitSet, [] external_constraints: rustc_middle::traits::solve::ExternalConstraintsData<'tcx>, + [decode] doc_link_resolutions: rustc_hir::def::DocLinkResMap, ]); ) } diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 0a16ede64991d..d37d6b37a37c3 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -2156,4 +2156,16 @@ rustc_queries! { desc { |tcx| "deducing parameter attributes for {}", tcx.def_path_str(def_id) } separate_provide_extern } + + query doc_link_resolutions(def_id: DefId) -> &'tcx DocLinkResMap { + eval_always + desc { "resolutions for documentation links for a module" } + separate_provide_extern + } + + query doc_link_traits_in_scope(def_id: DefId) -> &'tcx [DefId] { + eval_always + desc { "traits in scope for documentation links for a module" } + separate_provide_extern + } } diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 09c3d5b736cf1..cff3ba194bdb2 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -36,7 +36,7 @@ use rustc_data_structures::intern::Interned; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::tagged_ptr::CopyTaggedPtr; use rustc_hir as hir; -use rustc_hir::def::{CtorKind, CtorOf, DefKind, LifetimeRes, Res}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, DocLinkResMap, LifetimeRes, Res}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdMap}; use rustc_hir::Node; use rustc_index::vec::IndexVec; @@ -181,6 +181,8 @@ pub struct ResolverGlobalCtxt { /// exist under `std`. For example, wrote `str::from_utf8` instead of `std::str::from_utf8`. pub confused_type_with_std_module: FxHashMap, pub registered_tools: RegisteredTools, + pub doc_link_resolutions: FxHashMap, + pub doc_link_traits_in_scope: FxHashMap>, } /// Resolutions that should only be used for lowering. diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs index 84edb5f2a4288..303675d3ca5c1 100644 --- a/compiler/rustc_middle/src/ty/parameterized.rs +++ b/compiler/rustc_middle/src/ty/parameterized.rs @@ -81,6 +81,8 @@ trivially_parameterized_over_tcx! { rustc_hir::IsAsync, rustc_hir::LangItem, rustc_hir::def::DefKind, + rustc_hir::def::DocLinkResMap, + rustc_hir::def_id::DefId, rustc_hir::def_id::DefIndex, rustc_hir::definitions::DefKey, rustc_index::bit_set::BitSet, diff --git a/compiler/rustc_middle/src/ty/query.rs b/compiler/rustc_middle/src/ty/query.rs index 933aaadd62e1d..bec70974dde04 100644 --- a/compiler/rustc_middle/src/ty/query.rs +++ b/compiler/rustc_middle/src/ty/query.rs @@ -45,7 +45,7 @@ use rustc_data_structures::sync::Lrc; use rustc_data_structures::unord::UnordSet; use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; -use rustc_hir::def::DefKind; +use rustc_hir::def::{DefKind, DocLinkResMap}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet, LocalDefId}; use rustc_hir::hir_id::OwnerId; use rustc_hir::lang_items::{LangItem, LanguageItems}; diff --git a/compiler/rustc_resolve/Cargo.toml b/compiler/rustc_resolve/Cargo.toml index 7c3a0f8f277b5..d4935b52b1044 100644 --- a/compiler/rustc_resolve/Cargo.toml +++ b/compiler/rustc_resolve/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] bitflags = "1.2.1" +pulldown-cmark = { version = "0.9.2", default-features = false } rustc_arena = { path = "../rustc_arena" } rustc_ast = { path = "../rustc_ast" } rustc_ast_pretty = { path = "../rustc_ast_pretty" } diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 2fb62ce53ba6e..e74bb0a9a4f32 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -95,7 +95,7 @@ impl<'a> Resolver<'a> { /// Reachable macros with block module parents exist due to `#[macro_export] macro_rules!`, /// but they cannot use def-site hygiene, so the assumption holds /// (). - pub fn get_nearest_non_block_module(&mut self, mut def_id: DefId) -> Module<'a> { + pub(crate) fn get_nearest_non_block_module(&mut self, mut def_id: DefId) -> Module<'a> { loop { match self.get_module(def_id) { Some(module) => return module, @@ -104,7 +104,7 @@ impl<'a> Resolver<'a> { } } - pub fn expect_module(&mut self, def_id: DefId) -> Module<'a> { + pub(crate) fn expect_module(&mut self, def_id: DefId) -> Module<'a> { self.get_module(def_id).expect("argument `DefId` is not a module") } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 3ca10ac50baa6..ed1e787f3962c 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -8,7 +8,7 @@ use RibKind::*; -use crate::{path_names_to_string, BindingError, Finalize, LexicalScopeBinding}; +use crate::{path_names_to_string, rustdoc, BindingError, Finalize, LexicalScopeBinding}; use crate::{Module, ModuleOrUniformRoot, NameBinding, ParentScope, PathResult}; use crate::{ResolutionError, Resolver, Segment, UseError}; @@ -24,9 +24,10 @@ use rustc_hir::{BindingAnnotation, PrimTy, TraitCandidate}; use rustc_middle::middle::resolve_lifetime::Set1; use rustc_middle::ty::DefIdTree; use rustc_middle::{bug, span_bug}; +use rustc_session::config::CrateType; use rustc_session::lint; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_span::{BytePos, Span}; +use rustc_span::{BytePos, Span, SyntaxContext}; use smallvec::{smallvec, SmallVec}; use rustc_span::source_map::{respan, Spanned}; @@ -620,7 +621,9 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> { self.resolve_arm(arm); } fn visit_block(&mut self, block: &'ast Block) { + let old_macro_rules = self.parent_scope.macro_rules; self.resolve_block(block); + self.parent_scope.macro_rules = old_macro_rules; } fn visit_anon_const(&mut self, constant: &'ast AnonConst) { // We deal with repeat expressions explicitly in `resolve_expr`. @@ -771,6 +774,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> { ); } fn visit_foreign_item(&mut self, foreign_item: &'ast ForeignItem) { + self.resolve_doc_links(&foreign_item.attrs); match foreign_item.kind { ForeignItemKind::TyAlias(box TyAlias { ref generics, .. }) => { self.with_generic_param_rib( @@ -1159,6 +1163,16 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> { }) }); } + + fn visit_variant(&mut self, v: &'ast Variant) { + self.resolve_doc_links(&v.attrs); + visit::walk_variant(self, v) + } + + fn visit_field_def(&mut self, f: &'ast FieldDef) { + self.resolve_doc_links(&f.attrs); + visit::walk_field_def(self, f) + } } impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { @@ -2185,6 +2199,8 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { } fn resolve_item(&mut self, item: &'ast Item) { + self.resolve_doc_links(&item.attrs); + let name = item.ident.name; debug!("(resolving item) resolving {} ({:?})", name, item.kind); @@ -2274,9 +2290,18 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { ); } - ItemKind::Mod(..) | ItemKind::ForeignMod(_) => { + ItemKind::Mod(..) => { self.with_scope(item.id, |this| { + this.resolve_doc_links(&item.attrs); + let old_macro_rules = this.parent_scope.macro_rules; visit::walk_item(this, item); + // Maintain macro_rules scopes in the same way as during early resolution + // for diagnostics and doc links. + if item.attrs.iter().all(|attr| { + !attr.has_name(sym::macro_use) && !attr.has_name(sym::macro_escape) + }) { + this.parent_scope.macro_rules = old_macro_rules; + } }); } @@ -2309,14 +2334,22 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { self.future_proof_import(use_tree); } - ItemKind::ExternCrate(..) | ItemKind::MacroDef(..) => { - // do nothing, these are just around to be encoded + ItemKind::MacroDef(ref macro_def) => { + // Maintain macro_rules scopes in the same way as during early resolution + // for diagnostics and doc links. + if macro_def.macro_rules { + let (macro_rules_scope, _) = + self.r.macro_rules_scope(self.r.local_def_id(item.id)); + self.parent_scope.macro_rules = macro_rules_scope; + } } - ItemKind::GlobalAsm(_) => { + ItemKind::ForeignMod(_) | ItemKind::GlobalAsm(_) => { visit::walk_item(self, item); } + ItemKind::ExternCrate(..) => {} + ItemKind::MacCall(_) => panic!("unexpanded macro in resolve!"), } } @@ -2544,6 +2577,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { }; for item in trait_items { + self.resolve_doc_links(&item.attrs); match &item.kind { AssocItemKind::Const(_, ty, default) => { self.visit_ty(ty); @@ -2714,6 +2748,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { seen_trait_items: &mut FxHashMap, ) { use crate::ResolutionError::*; + self.resolve_doc_links(&item.attrs); match &item.kind { AssocItemKind::Const(_, ty, default) => { debug!("resolve_implementation AssocItemKind::Const"); @@ -4116,6 +4151,86 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { self.r.extra_lifetime_params_map.insert(async_node_id, extra_lifetime_params); } } + + fn resolve_and_cache_rustdoc_path(&mut self, path_str: &str, ns: Namespace) -> bool { + // FIXME: This caching may be incorrect in case of multiple `macro_rules` + // items with the same name in the same module. + // Also hygiene is not considered. + let mut doc_link_resolutions = std::mem::take(&mut self.r.doc_link_resolutions); + let res = doc_link_resolutions + .entry(self.parent_scope.module.nearest_parent_mod().expect_local()) + .or_default() + .entry((Symbol::intern(path_str), ns)) + .or_insert_with_key(|(path, ns)| { + let res = self.r.resolve_rustdoc_path(path.as_str(), *ns, self.parent_scope); + if let Some(res) = res + && let Some(def_id) = res.opt_def_id() + && !def_id.is_local() + && self.r.session.crate_types().contains(&CrateType::ProcMacro) { + // Encoding foreign def ids in proc macro crate metadata will ICE. + return None; + } + res + }) + .is_some(); + self.r.doc_link_resolutions = doc_link_resolutions; + res + } + + fn resolve_doc_links(&mut self, attrs: &[Attribute]) { + if !attrs.iter().any(|attr| attr.may_have_doc_links()) { + return; + } + + let mut need_traits_in_scope = false; + for path_str in rustdoc::attrs_to_preprocessed_links(attrs) { + // Resolve all namespaces due to no disambiguator or for diagnostics. + let mut any_resolved = false; + let mut need_assoc = false; + for ns in [TypeNS, ValueNS, MacroNS] { + if self.resolve_and_cache_rustdoc_path(&path_str, ns) { + any_resolved = true; + } else if ns != MacroNS { + need_assoc = true; + } + } + + // Resolve all prefixes for type-relative resolution or for diagnostics. + if need_assoc || !any_resolved { + let mut path = &path_str[..]; + while let Some(idx) = path.rfind("::") { + path = &path[..idx]; + need_traits_in_scope = true; + for ns in [TypeNS, ValueNS, MacroNS] { + self.resolve_and_cache_rustdoc_path(path, ns); + } + } + } + } + + if need_traits_in_scope { + // FIXME: hygiene is not considered. + let mut doc_link_traits_in_scope = std::mem::take(&mut self.r.doc_link_traits_in_scope); + doc_link_traits_in_scope + .entry(self.parent_scope.module.nearest_parent_mod().expect_local()) + .or_insert_with(|| { + self.r + .traits_in_scope(None, &self.parent_scope, SyntaxContext::root(), None) + .into_iter() + .filter_map(|tr| { + if !tr.def_id.is_local() + && self.r.session.crate_types().contains(&CrateType::ProcMacro) + { + // Encoding foreign def ids in proc macro crate metadata will ICE. + return None; + } + Some(tr.def_id) + }) + .collect() + }); + self.r.doc_link_traits_in_scope = doc_link_traits_in_scope; + } + } } struct LifetimeCountVisitor<'a, 'b> { @@ -4162,6 +4277,7 @@ impl<'a> Resolver<'a> { pub(crate) fn late_resolve_crate(&mut self, krate: &Crate) { visit::walk_crate(&mut LifetimeCountVisitor { r: self }, krate); let mut late_resolution_visitor = LateResolutionVisitor::new(self); + late_resolution_visitor.resolve_doc_links(&krate.attrs); visit::walk_crate(&mut late_resolution_visitor, krate); for (id, span) in late_resolution_visitor.diagnostic_metadata.unused_labels.iter() { self.lint_buffer.buffer_lint(lint::builtin::UNUSED_LABELS, *id, *span, "unused label"); diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 1b181b714005b..e61e83189c384 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -33,7 +33,7 @@ use rustc_data_structures::sync::{Lrc, RwLock}; use rustc_errors::{Applicability, DiagnosticBuilder, ErrorGuaranteed}; use rustc_expand::base::{DeriveResolutions, SyntaxExtension, SyntaxExtensionKind}; use rustc_hir::def::Namespace::*; -use rustc_hir::def::{self, CtorOf, DefKind, LifetimeRes, PartialRes}; +use rustc_hir::def::{self, CtorOf, DefKind, DocLinkResMap, LifetimeRes, PartialRes}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId}; use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; use rustc_hir::definitions::{DefPathData, Definitions}; @@ -78,6 +78,7 @@ mod ident; mod imports; mod late; mod macros; +pub mod rustdoc; enum Weak { Yes, @@ -138,17 +139,17 @@ enum ScopeSet<'a> { /// This struct is currently used only for early resolution (imports and macros), /// but not for late resolution yet. #[derive(Clone, Copy, Debug)] -pub struct ParentScope<'a> { - pub module: Module<'a>, +struct ParentScope<'a> { + module: Module<'a>, expansion: LocalExpnId, - pub macro_rules: MacroRulesScopeRef<'a>, + macro_rules: MacroRulesScopeRef<'a>, derives: &'a [ast::Path], } impl<'a> ParentScope<'a> { /// Creates a parent scope with the passed argument used as the module scope component, /// and other scope components set to default empty values. - pub fn module(module: Module<'a>, resolver: &Resolver<'a>) -> ParentScope<'a> { + fn module(module: Module<'a>, resolver: &Resolver<'a>) -> ParentScope<'a> { ParentScope { module, expansion: LocalExpnId::ROOT, @@ -1046,6 +1047,8 @@ pub struct Resolver<'a> { lifetime_elision_allowed: FxHashSet, effective_visibilities: EffectiveVisibilities, + doc_link_resolutions: FxHashMap, + doc_link_traits_in_scope: FxHashMap>, } /// Nothing really interesting here; it just provides memory for the rest of the crate. @@ -1374,6 +1377,8 @@ impl<'a> Resolver<'a> { confused_type_with_std_module: Default::default(), lifetime_elision_allowed: Default::default(), effective_visibilities: Default::default(), + doc_link_resolutions: Default::default(), + doc_link_traits_in_scope: Default::default(), }; let root_parent_scope = ParentScope::module(graph_root, &resolver); @@ -1450,6 +1455,8 @@ impl<'a> Resolver<'a> { proc_macros, confused_type_with_std_module, registered_tools: self.registered_tools, + doc_link_resolutions: self.doc_link_resolutions, + doc_link_traits_in_scope: self.doc_link_traits_in_scope, }; let ast_lowering = ty::ResolverAstLowering { legacy_const_generic_args: self.legacy_const_generic_args, @@ -1494,6 +1501,8 @@ impl<'a> Resolver<'a> { confused_type_with_std_module: self.confused_type_with_std_module.clone(), registered_tools: self.registered_tools.clone(), effective_visibilities: self.effective_visibilities.clone(), + doc_link_resolutions: self.doc_link_resolutions.clone(), + doc_link_traits_in_scope: self.doc_link_traits_in_scope.clone(), }; let ast_lowering = ty::ResolverAstLowering { legacy_const_generic_args: self.legacy_const_generic_args.clone(), @@ -1575,7 +1584,7 @@ impl<'a> Resolver<'a> { }); } - pub fn traits_in_scope( + fn traits_in_scope( &mut self, current_trait: Option>, parent_scope: &ParentScope<'a>, @@ -1927,7 +1936,7 @@ impl<'a> Resolver<'a> { /// isn't something that can be returned because it can't be made to live that long, /// and also it's a private type. Fortunately rustdoc doesn't need to know the error, /// just that an error occurred. - pub fn resolve_rustdoc_path( + fn resolve_rustdoc_path( &mut self, path_str: &str, ns: Namespace, @@ -1959,16 +1968,6 @@ impl<'a> Resolver<'a> { } } - /// For rustdoc. - /// For local modules returns only reexports, for external modules returns all children. - pub fn module_children_or_reexports(&self, def_id: DefId) -> Vec { - if let Some(def_id) = def_id.as_local() { - self.reexport_map.get(&def_id).cloned().unwrap_or_default() - } else { - self.cstore().module_children_untracked(def_id, self.session).collect() - } - } - /// For rustdoc. pub fn macro_rules_scope(&self, def_id: LocalDefId) -> (MacroRulesScopeRef<'a>, Res) { let scope = *self.macro_rules_scopes.get(&def_id).expect("not a `macro_rules` item"); @@ -1978,11 +1977,6 @@ impl<'a> Resolver<'a> { } } - /// For rustdoc. - pub fn get_partial_res(&self, node_id: NodeId) -> Option { - self.partial_res_map.get(&node_id).copied() - } - /// Retrieves the span of the given `DefId` if `DefId` is in the local crate. #[inline] pub fn opt_span(&self, def_id: DefId) -> Option { diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index b5b1602c5e0d3..0c2e8be049884 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -568,7 +568,7 @@ impl<'a> Resolver<'a> { Ok((ext, res)) } - pub fn resolve_macro_path( + pub(crate) fn resolve_macro_path( &mut self, path: &ast::Path, kind: Option, diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs new file mode 100644 index 0000000000000..86172afb87693 --- /dev/null +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -0,0 +1,361 @@ +use pulldown_cmark::{BrokenLink, Event, Options, Parser, Tag}; +use rustc_ast as ast; +use rustc_ast::util::comments::beautify_doc_string; +use rustc_data_structures::fx::FxHashMap; +use rustc_span::def_id::DefId; +use rustc_span::symbol::{kw, Symbol}; +use rustc_span::Span; +use std::cell::RefCell; +use std::{cmp, mem}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum DocFragmentKind { + /// A doc fragment created from a `///` or `//!` doc comment. + SugaredDoc, + /// A doc fragment created from a "raw" `#[doc=""]` attribute. + RawDoc, +} + +/// A portion of documentation, extracted from a `#[doc]` attribute. +/// +/// Each variant contains the line number within the complete doc-comment where the fragment +/// starts, as well as the Span where the corresponding doc comment or attribute is located. +/// +/// Included files are kept separate from inline doc comments so that proper line-number +/// information can be given when a doctest fails. Sugared doc comments and "raw" doc comments are +/// kept separate because of issue #42760. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct DocFragment { + pub span: Span, + /// The module this doc-comment came from. + /// + /// This allows distinguishing between the original documentation and a pub re-export. + /// If it is `None`, the item was not re-exported. + pub parent_module: Option, + pub doc: Symbol, + pub kind: DocFragmentKind, + pub indent: usize, +} + +#[derive(Clone, Copy, Debug)] +pub enum MalformedGenerics { + /// This link has unbalanced angle brackets. + /// + /// For example, `Vec>`. + UnbalancedAngleBrackets, + /// The generics are not attached to a type. + /// + /// For example, `` should trigger this. + /// + /// This is detected by checking if the path is empty after the generics are stripped. + MissingType, + /// The link uses fully-qualified syntax, which is currently unsupported. + /// + /// For example, `::into_iter` should trigger this. + /// + /// This is detected by checking if ` as ` (the keyword `as` with spaces around it) is inside + /// angle brackets. + HasFullyQualifiedSyntax, + /// The link has an invalid path separator. + /// + /// For example, `Vec::new()` should trigger this. Note that `Vec:new()` will **not** + /// trigger this because it has no generics and thus [`strip_generics_from_path`] will not be + /// called. + /// + /// Note that this will also **not** be triggered if the invalid path separator is inside angle + /// brackets because rustdoc mostly ignores what's inside angle brackets (except for + /// [`HasFullyQualifiedSyntax`](MalformedGenerics::HasFullyQualifiedSyntax)). + /// + /// This is detected by checking if there is a colon followed by a non-colon in the link. + InvalidPathSeparator, + /// The link has too many angle brackets. + /// + /// For example, `Vec<>` should trigger this. + TooManyAngleBrackets, + /// The link has empty angle brackets. + /// + /// For example, `Vec<>` should trigger this. + EmptyAngleBrackets, +} + +/// Removes excess indentation on comments in order for the Markdown +/// to be parsed correctly. This is necessary because the convention for +/// writing documentation is to provide a space between the /// or //! marker +/// and the doc text, but Markdown is whitespace-sensitive. For example, +/// a block of text with four-space indentation is parsed as a code block, +/// so if we didn't unindent comments, these list items +/// +/// /// A list: +/// /// +/// /// - Foo +/// /// - Bar +/// +/// would be parsed as if they were in a code block, which is likely not what the user intended. +pub fn unindent_doc_fragments(docs: &mut [DocFragment]) { + // `add` is used in case the most common sugared doc syntax is used ("/// "). The other + // fragments kind's lines are never starting with a whitespace unless they are using some + // markdown formatting requiring it. Therefore, if the doc block have a mix between the two, + // we need to take into account the fact that the minimum indent minus one (to take this + // whitespace into account). + // + // For example: + // + // /// hello! + // #[doc = "another"] + // + // In this case, you want "hello! another" and not "hello! another". + let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind) + && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc) + { + // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to + // "decide" how much the minimum indent will be. + 1 + } else { + 0 + }; + + // `min_indent` is used to know how much whitespaces from the start of each lines must be + // removed. Example: + // + // /// hello! + // #[doc = "another"] + // + // In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum + // 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4 + // (5 - 1) whitespaces. + let Some(min_indent) = docs + .iter() + .map(|fragment| { + fragment.doc.as_str().lines().fold(usize::MAX, |min_indent, line| { + if line.chars().all(|c| c.is_whitespace()) { + min_indent + } else { + // Compare against either space or tab, ignoring whether they are + // mixed or not. + let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count(); + cmp::min(min_indent, whitespace) + + if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add } + } + }) + }) + .min() + else { + return; + }; + + for fragment in docs { + if fragment.doc == kw::Empty { + continue; + } + + let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 { + min_indent - add + } else { + min_indent + }; + + fragment.indent = min_indent; + } +} + +/// The goal of this function is to apply the `DocFragment` transformation that is required when +/// transforming into the final Markdown, which is applying the computed indent to each line in +/// each doc fragment (a `DocFragment` can contain multiple lines in case of `#[doc = ""]`). +/// +/// Note: remove the trailing newline where appropriate +pub fn add_doc_fragment(out: &mut String, frag: &DocFragment) { + let s = frag.doc.as_str(); + let mut iter = s.lines(); + if s.is_empty() { + out.push('\n'); + return; + } + while let Some(line) = iter.next() { + if line.chars().any(|c| !c.is_whitespace()) { + assert!(line.len() >= frag.indent); + out.push_str(&line[frag.indent..]); + } else { + out.push_str(line); + } + out.push('\n'); + } +} + +pub fn attrs_to_doc_fragments<'a>( + attrs: impl Iterator)>, + doc_only: bool, +) -> (Vec, ast::AttrVec) { + let mut doc_fragments = Vec::new(); + let mut other_attrs = ast::AttrVec::new(); + for (attr, parent_module) in attrs { + if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() { + let doc = beautify_doc_string(doc_str, comment_kind); + let kind = if attr.is_doc_comment() { + DocFragmentKind::SugaredDoc + } else { + DocFragmentKind::RawDoc + }; + let fragment = DocFragment { span: attr.span, doc, kind, parent_module, indent: 0 }; + doc_fragments.push(fragment); + } else if !doc_only { + other_attrs.push(attr.clone()); + } + } + + unindent_doc_fragments(&mut doc_fragments); + + (doc_fragments, other_attrs) +} + +/// Return the doc-comments on this item, grouped by the module they came from. +/// The module can be different if this is a re-export with added documentation. +/// +/// The last newline is not trimmed so the produced strings are reusable between +/// early and late doc link resolution regardless of their position. +pub fn prepare_to_doc_link_resolution( + doc_fragments: &[DocFragment], +) -> FxHashMap, String> { + let mut res = FxHashMap::default(); + for fragment in doc_fragments { + let out_str = res.entry(fragment.parent_module).or_default(); + add_doc_fragment(out_str, fragment); + } + res +} + +/// Options for rendering Markdown in the main body of documentation. +pub fn main_body_opts() -> Options { + Options::ENABLE_TABLES + | Options::ENABLE_FOOTNOTES + | Options::ENABLE_STRIKETHROUGH + | Options::ENABLE_TASKLISTS + | Options::ENABLE_SMART_PUNCTUATION +} + +fn strip_generics_from_path_segment(segment: Vec) -> Result { + let mut stripped_segment = String::new(); + let mut param_depth = 0; + + let mut latest_generics_chunk = String::new(); + + for c in segment { + if c == '<' { + param_depth += 1; + latest_generics_chunk.clear(); + } else if c == '>' { + param_depth -= 1; + 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(MalformedGenerics::HasFullyQualifiedSyntax); + } + } else { + if param_depth == 0 { + stripped_segment.push(c); + } else { + latest_generics_chunk.push(c); + } + } + } + + if param_depth == 0 { + Ok(stripped_segment) + } else { + // The segment has unbalanced angle brackets, e.g. `Vec>` + Err(MalformedGenerics::UnbalancedAngleBrackets) + } +} + +pub fn strip_generics_from_path(path_str: &str) -> Result { + if !path_str.contains(['<', '>']) { + return Ok(path_str.to_string()); + } + let mut stripped_segments = vec![]; + let mut path = path_str.chars().peekable(); + let mut segment = Vec::new(); + + while let Some(chr) = path.next() { + match chr { + ':' => { + if path.next_if_eq(&':').is_some() { + let stripped_segment = + strip_generics_from_path_segment(mem::take(&mut segment))?; + if !stripped_segment.is_empty() { + stripped_segments.push(stripped_segment); + } + } else { + return Err(MalformedGenerics::InvalidPathSeparator); + } + } + '<' => { + segment.push(chr); + + match path.next() { + Some('<') => { + return Err(MalformedGenerics::TooManyAngleBrackets); + } + Some('>') => { + return Err(MalformedGenerics::EmptyAngleBrackets); + } + Some(chr) => { + segment.push(chr); + + while let Some(chr) = path.next_if(|c| *c != '>') { + segment.push(chr); + } + } + None => break, + } + } + _ => segment.push(chr), + } + trace!("raw segment: {:?}", segment); + } + + if !segment.is_empty() { + let stripped_segment = strip_generics_from_path_segment(segment)?; + if !stripped_segment.is_empty() { + stripped_segments.push(stripped_segment); + } + } + + debug!("path_str: {:?}\nstripped segments: {:?}", path_str, &stripped_segments); + + let stripped_path = stripped_segments.join("::"); + + if !stripped_path.is_empty() { Ok(stripped_path) } else { Err(MalformedGenerics::MissingType) } +} + +/// Simplified version of the corresponding function in rustdoc. +/// If the rustdoc version returns a successful result, this function must return the same result. +/// Otherwise this function may return anything. +fn preprocess_link(link: &str) -> String { + let link = link.replace('`', ""); + let link = link.split('#').next().unwrap(); + let link = link.rsplit('@').next().unwrap(); + let link = link.strip_suffix("()").unwrap_or(link); + let link = link.strip_suffix("{}").unwrap_or(link); + let link = link.strip_suffix("[]").unwrap_or(link); + let link = if link != "!" { link.strip_suffix("!").unwrap_or(link) } else { link }; + strip_generics_from_path(link).unwrap_or_else(|_| link.to_string()) +} + +/// Simplified version of `preprocessed_markdown_links` from rustdoc. +/// Must return at least the same links as it, but may add some more links on top of that. +pub(crate) fn attrs_to_preprocessed_links(attrs: &[ast::Attribute]) -> Vec { + let (doc_fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true); + let doc = prepare_to_doc_link_resolution(&doc_fragments).into_values().next().unwrap(); + + let links = RefCell::new(Vec::new()); + let mut callback = |link: BrokenLink<'_>| { + links.borrow_mut().push(preprocess_link(&link.reference)); + None + }; + for event in Parser::new_with_broken_link_callback(&doc, main_body_opts(), Some(&mut callback)) + { + if let Event::Start(Tag::Link(_, dest, _)) = event { + links.borrow_mut().push(preprocess_link(&dest)); + } + } + links.into_inner() +} diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 0271c27b4f522..5e592227d49c5 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -12,7 +12,6 @@ askama = { version = "0.11", default-features = false, features = ["config"] } itertools = "0.10.1" minifier = "0.2.2" once_cell = "1.10.0" -pulldown-cmark = { version = "0.9.2", default-features = false } regex = "1" rustdoc-json-types = { path = "../rustdoc-json-types" } serde_json = "1.0" diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 85dd3881593ac..de350af515948 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -5,12 +5,11 @@ use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; use std::sync::OnceLock as OnceCell; -use std::{cmp, fmt, iter}; +use std::{fmt, iter}; use arrayvec::ArrayVec; use thin_vec::ThinVec; -use rustc_ast::util::comments::beautify_doc_string; use rustc_ast::{self as ast, AttrStyle}; use rustc_attr::{ConstStability, Deprecation, Stability, StabilityLevel}; use rustc_const_eval::const_eval::is_unstable_const_fn; @@ -24,6 +23,7 @@ use rustc_hir_analysis::check::intrinsic::intrinsic_operation_unsafety; use rustc_index::vec::IndexVec; use rustc_middle::ty::fast_reject::SimplifiedType; use rustc_middle::ty::{self, DefIdTree, TyCtxt, Visibility}; +use rustc_resolve::rustdoc::{add_doc_fragment, attrs_to_doc_fragments, DocFragment}; use rustc_session::Session; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Ident, Symbol}; @@ -1010,58 +1010,6 @@ impl> NestedAttributesExt for I { } } -/// A portion of documentation, extracted from a `#[doc]` attribute. -/// -/// Each variant contains the line number within the complete doc-comment where the fragment -/// starts, as well as the Span where the corresponding doc comment or attribute is located. -/// -/// Included files are kept separate from inline doc comments so that proper line-number -/// information can be given when a doctest fails. Sugared doc comments and "raw" doc comments are -/// kept separate because of issue #42760. -#[derive(Clone, PartialEq, Eq, Debug)] -pub(crate) struct DocFragment { - pub(crate) span: rustc_span::Span, - /// The module this doc-comment came from. - /// - /// This allows distinguishing between the original documentation and a pub re-export. - /// If it is `None`, the item was not re-exported. - pub(crate) parent_module: Option, - pub(crate) doc: Symbol, - pub(crate) kind: DocFragmentKind, - pub(crate) indent: usize, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub(crate) enum DocFragmentKind { - /// A doc fragment created from a `///` or `//!` doc comment. - SugaredDoc, - /// A doc fragment created from a "raw" `#[doc=""]` attribute. - RawDoc, -} - -/// The goal of this function is to apply the `DocFragment` transformation that is required when -/// transforming into the final Markdown, which is applying the computed indent to each line in -/// each doc fragment (a `DocFragment` can contain multiple lines in case of `#[doc = ""]`). -/// -/// Note: remove the trailing newline where appropriate -fn add_doc_fragment(out: &mut String, frag: &DocFragment) { - let s = frag.doc.as_str(); - let mut iter = s.lines(); - if s.is_empty() { - out.push('\n'); - return; - } - while let Some(line) = iter.next() { - if line.chars().any(|c| !c.is_whitespace()) { - assert!(line.len() >= frag.indent); - out.push_str(&line[frag.indent..]); - } else { - out.push_str(line); - } - out.push('\n'); - } -} - /// Collapse a collection of [`DocFragment`]s into one string, /// handling indentation and newlines as needed. pub(crate) fn collapse_doc_fragments(doc_strings: &[DocFragment]) -> String { @@ -1073,86 +1021,6 @@ pub(crate) fn collapse_doc_fragments(doc_strings: &[DocFragment]) -> String { acc } -/// Removes excess indentation on comments in order for the Markdown -/// to be parsed correctly. This is necessary because the convention for -/// writing documentation is to provide a space between the /// or //! marker -/// and the doc text, but Markdown is whitespace-sensitive. For example, -/// a block of text with four-space indentation is parsed as a code block, -/// so if we didn't unindent comments, these list items -/// -/// /// A list: -/// /// -/// /// - Foo -/// /// - Bar -/// -/// would be parsed as if they were in a code block, which is likely not what the user intended. -fn unindent_doc_fragments(docs: &mut Vec) { - // `add` is used in case the most common sugared doc syntax is used ("/// "). The other - // fragments kind's lines are never starting with a whitespace unless they are using some - // markdown formatting requiring it. Therefore, if the doc block have a mix between the two, - // we need to take into account the fact that the minimum indent minus one (to take this - // whitespace into account). - // - // For example: - // - // /// hello! - // #[doc = "another"] - // - // In this case, you want "hello! another" and not "hello! another". - let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind) - && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc) - { - // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to - // "decide" how much the minimum indent will be. - 1 - } else { - 0 - }; - - // `min_indent` is used to know how much whitespaces from the start of each lines must be - // removed. Example: - // - // /// hello! - // #[doc = "another"] - // - // In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum - // 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4 - // (5 - 1) whitespaces. - let Some(min_indent) = docs - .iter() - .map(|fragment| { - fragment.doc.as_str().lines().fold(usize::MAX, |min_indent, line| { - if line.chars().all(|c| c.is_whitespace()) { - min_indent - } else { - // Compare against either space or tab, ignoring whether they are - // mixed or not. - let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count(); - cmp::min(min_indent, whitespace) - + if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add } - } - }) - }) - .min() - else { - return; - }; - - for fragment in docs { - if fragment.doc == kw::Empty { - continue; - } - - let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 { - min_indent - add - } else { - min_indent - }; - - fragment.indent = min_indent; - } -} - /// A link that has not yet been rendered. /// /// This link will be turned into a rendered link by [`Item::links`]. @@ -1231,26 +1099,7 @@ impl Attributes { attrs: impl Iterator)>, doc_only: bool, ) -> Attributes { - let mut doc_strings = Vec::new(); - let mut other_attrs = ast::AttrVec::new(); - for (attr, parent_module) in attrs { - if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() { - trace!("got doc_str={doc_str:?}"); - let doc = beautify_doc_string(doc_str, comment_kind); - let kind = if attr.is_doc_comment() { - DocFragmentKind::SugaredDoc - } else { - DocFragmentKind::RawDoc - }; - let fragment = DocFragment { span: attr.span, doc, kind, parent_module, indent: 0 }; - doc_strings.push(fragment); - } else if !doc_only { - other_attrs.push(attr.clone()); - } - } - - unindent_doc_fragments(&mut doc_strings); - + let (doc_strings, other_attrs) = attrs_to_doc_fragments(attrs, doc_only); Attributes { doc_strings, other_attrs } } @@ -1269,20 +1118,6 @@ impl Attributes { if out.is_empty() { None } else { Some(out) } } - /// Return the doc-comments on this item, grouped by the module they came from. - /// The module can be different if this is a re-export with added documentation. - /// - /// The last newline is not trimmed so the produced strings are reusable between - /// early and late doc link resolution regardless of their position. - pub(crate) fn prepare_to_doc_link_resolution(&self) -> FxHashMap, String> { - let mut res = FxHashMap::default(); - for fragment in &self.doc_strings { - let out_str = res.entry(fragment.parent_module).or_default(); - add_doc_fragment(out_str, fragment); - } - res - } - /// Finds all `doc` attributes as NameValues and returns their corresponding values, joined /// with newlines. pub(crate) fn collapsed_doc_value(&self) -> Option { diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index 71eddf4348f1e..20627c2cfc164 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -2,6 +2,7 @@ use super::*; use crate::clean::collapse_doc_fragments; +use rustc_resolve::rustdoc::{unindent_doc_fragments, DocFragment, DocFragmentKind}; use rustc_span::create_default_session_globals_then; use rustc_span::source_map::DUMMY_SP; use rustc_span::symbol::Symbol; diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 0ce43f7db8e8b..e8316ac976b5f 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -5,14 +5,13 @@ use rustc_data_structures::unord::UnordSet; use rustc_errors::emitter::{Emitter, EmitterWriter}; use rustc_errors::json::JsonEmitter; use rustc_feature::UnstableFeatures; -use rustc_hir::def::{Namespace, Res}; +use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId}; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{HirId, Path, TraitCandidate}; +use rustc_hir::{HirId, Path}; use rustc_interface::interface; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; -use rustc_resolve as resolve; use rustc_session::config::{self, CrateType, ErrorOutputType}; use rustc_session::lint; use rustc_session::Session; @@ -35,10 +34,6 @@ pub(crate) use rustc_session::config::{Input, Options, UnstableOptions}; pub(crate) struct ResolverCaches { pub(crate) markdown_links: Option>>, - pub(crate) doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option>>, - /// Traits in scope for a given module. - /// See `collect_intra_doc_links::traits_implemented_by` for more details. - pub(crate) traits_in_scope: DefIdMap>, pub(crate) all_trait_impls: Option>, pub(crate) all_macro_rules: FxHashMap>, pub(crate) extern_doc_reachable: DefIdSet, @@ -46,12 +41,6 @@ pub(crate) struct ResolverCaches { pub(crate) struct DocContext<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, - /// Name resolver. Used for intra-doc links. - /// - /// The `Rc>` wrapping is needed because that is what's returned by - /// [`rustc_interface::Queries::expansion()`]. - // FIXME: see if we can get rid of this RefCell somehow - pub(crate) resolver: Rc>, pub(crate) resolver_caches: ResolverCaches, /// Used for normalization. /// @@ -100,13 +89,6 @@ impl<'tcx> DocContext<'tcx> { ret } - pub(crate) fn enter_resolver(&self, f: F) -> R - where - F: FnOnce(&mut resolve::Resolver<'_>) -> R, - { - self.resolver.borrow_mut().access(f) - } - /// Call the closure with the given parameters set as /// the substitutions for a type alias' RHS. pub(crate) fn enter_alias(&mut self, substs: DefIdMap, f: F) -> R @@ -313,7 +295,6 @@ pub(crate) fn create_config( pub(crate) fn run_global_ctxt( tcx: TyCtxt<'_>, - resolver: Rc>, resolver_caches: ResolverCaches, show_coverage: bool, render_options: RenderOptions, @@ -348,7 +329,6 @@ pub(crate) fn run_global_ctxt( let mut ctxt = DocContext { tcx, - resolver, resolver_caches, param_env: ParamEnv::empty(), external_traits: Default::default(), diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 94de93e7a9916..dee0a01a65413 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -28,6 +28,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; +pub(crate) use rustc_resolve::rustdoc::main_body_opts; use rustc_span::edition::Edition; use rustc_span::{Span, Symbol}; @@ -58,15 +59,6 @@ mod tests; const MAX_HEADER_LEVEL: u32 = 6; -/// Options for rendering Markdown in the main body of documentation. -pub(crate) fn main_body_opts() -> Options { - Options::ENABLE_TABLES - | Options::ENABLE_FOOTNOTES - | Options::ENABLE_STRIKETHROUGH - | Options::ENABLE_TASKLISTS - | Options::ENABLE_SMART_PUNCTUATION -} - /// Options for rendering Markdown in summaries (e.g., in search results). pub(crate) fn summary_opts() -> Options { Options::ENABLE_TABLES diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 64108c8828518..0d4a87eb4fe3a 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -31,6 +31,7 @@ extern crate tracing; // // Dependencies listed in Cargo.toml do not need `extern crate`. +extern crate pulldown_cmark; extern crate rustc_ast; extern crate rustc_ast_pretty; extern crate rustc_attr; @@ -792,22 +793,13 @@ fn main_args(at_args: &[String]) -> MainResult { } compiler.enter(|queries| { - // We need to hold on to the complete resolver, so we cause everything to be - // cloned for the analysis passes to use. Suboptimal, but necessary in the - // current architecture. - // FIXME(#83761): Resolver cloning can lead to inconsistencies between data in the - // two copies because one of the copies can be modified after `TyCtxt` construction. - let (resolver, resolver_caches) = { + let resolver_caches = { let expansion = abort_on_err(queries.expansion(), sess); let (krate, resolver, _) = &*expansion.borrow(); let resolver_caches = resolver.borrow_mut().access(|resolver| { - collect_intra_doc_links::early_resolve_intra_doc_links( - resolver, - krate, - render_options.document_private, - ) + collect_intra_doc_links::early_resolve_intra_doc_links(resolver, krate) }); - (resolver.clone(), resolver_caches) + resolver_caches }; if sess.diagnostic().has_errors_or_lint_errors().is_some() { @@ -820,7 +812,6 @@ fn main_args(at_args: &[String]) -> MainResult { let (krate, render_opts, mut cache) = sess.time("run_global_ctxt", || { core::run_global_ctxt( tcx, - resolver, resolver_caches, show_coverage, render_options, diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 8435972bb11f2..692adcf0a8091 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -15,7 +15,8 @@ use rustc_hir::def_id::{DefId, CRATE_DEF_ID}; use rustc_hir::Mutability; use rustc_middle::ty::{DefIdTree, Ty, TyCtxt}; use rustc_middle::{bug, ty}; -use rustc_resolve::ParentScope; +use rustc_resolve::rustdoc::MalformedGenerics; +use rustc_resolve::rustdoc::{prepare_to_doc_link_resolution, strip_generics_from_path}; use rustc_session::lint::Lint; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{sym, Ident, Symbol}; @@ -23,7 +24,6 @@ use rustc_span::BytePos; use smallvec::{smallvec, SmallVec}; use std::borrow::Cow; -use std::mem; use std::ops::Range; use crate::clean::{self, utils::find_nearest_parent_module}; @@ -179,47 +179,6 @@ enum ResolutionFailure<'a> { NotResolved(UnresolvedPath<'a>), } -#[derive(Clone, Copy, Debug)] -enum MalformedGenerics { - /// This link has unbalanced angle brackets. - /// - /// For example, `Vec>`. - UnbalancedAngleBrackets, - /// The generics are not attached to a type. - /// - /// For example, `` should trigger this. - /// - /// This is detected by checking if the path is empty after the generics are stripped. - MissingType, - /// The link uses fully-qualified syntax, which is currently unsupported. - /// - /// For example, `::into_iter` should trigger this. - /// - /// This is detected by checking if ` as ` (the keyword `as` with spaces around it) is inside - /// angle brackets. - HasFullyQualifiedSyntax, - /// The link has an invalid path separator. - /// - /// For example, `Vec::new()` should trigger this. Note that `Vec:new()` will **not** - /// trigger this because it has no generics and thus [`strip_generics_from_path`] will not be - /// called. - /// - /// Note that this will also **not** be triggered if the invalid path separator is inside angle - /// brackets because rustdoc mostly ignores what's inside angle brackets (except for - /// [`HasFullyQualifiedSyntax`](MalformedGenerics::HasFullyQualifiedSyntax)). - /// - /// This is detected by checking if there is a colon followed by a non-colon in the link. - InvalidPathSeparator, - /// The link has too many angle brackets. - /// - /// For example, `Vec<>` should trigger this. - TooManyAngleBrackets, - /// The link has empty angle brackets. - /// - /// For example, `Vec<>` should trigger this. - EmptyAngleBrackets, -} - #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub(crate) enum UrlFragment { Item(DefId), @@ -407,10 +366,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { }) } - /// Convenience wrapper around `resolve_rustdoc_path`. + /// Convenience wrapper around `doc_link_resolutions`. /// /// This also handles resolving `true` and `false` as booleans. - /// NOTE: `resolve_rustdoc_path` knows only about paths, not about types. + /// NOTE: `doc_link_resolutions` knows only about paths, not about types. /// Associated items will never be resolved by this function. fn resolve_path( &self, @@ -426,17 +385,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { // Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`). let result = self .cx - .resolver_caches - .doc_link_resolutions - .get(&(Symbol::intern(path_str), ns, module_id)) + .tcx + .doc_link_resolutions(module_id) + .get(&(Symbol::intern(path_str), ns)) .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) - }) - }) + .unwrap_or_else(|| panic!("no resolution for {:?} {:?} {:?}", path_str, ns, module_id)) .and_then(|res| res.try_into().ok()) .or_else(|| resolve_primitive(path_str, ns)); debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns); @@ -779,8 +732,7 @@ fn trait_impls_for<'a>( module: DefId, ) -> FxHashSet<(DefId, DefId)> { let tcx = cx.tcx; - let iter = cx.resolver_caches.traits_in_scope[&module].iter().flat_map(|trait_candidate| { - let trait_ = trait_candidate.def_id; + let iter = tcx.doc_link_traits_in_scope(module).iter().flat_map(|&trait_| { trace!("considering explicit impl for trait {:?}", trait_); // Look at each trait implementation to see if it's an impl for `did` @@ -846,7 +798,7 @@ impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> { // 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.prepare_to_doc_link_resolution() { + for (parent_module, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) { if !may_have_doc_links(&doc) { continue; } @@ -975,16 +927,12 @@ fn preprocess_link( } // Strip generics from the path. - let path_str = if path_str.contains(['<', '>'].as_slice()) { - match strip_generics_from_path(path_str) { - Ok(path) => path, - Err(err) => { - debug!("link has malformed generics: {}", path_str); - return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned()))); - } + let path_str = match strip_generics_from_path(path_str) { + Ok(path) => path, + Err(err) => { + debug!("link has malformed generics: {}", path_str); + return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned()))); } - } else { - path_str.to_owned() }; // Sanity check to make sure we don't have any angle brackets after stripping generics. @@ -2064,94 +2012,3 @@ fn resolve_primitive(path_str: &str, ns: Namespace) -> Option { debug!("resolved primitives {:?}", prim); Some(Res::Primitive(prim)) } - -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(); - - while let Some(chr) = path.next() { - match chr { - ':' => { - if path.next_if_eq(&':').is_some() { - let stripped_segment = - strip_generics_from_path_segment(mem::take(&mut segment))?; - if !stripped_segment.is_empty() { - stripped_segments.push(stripped_segment); - } - } else { - return Err(MalformedGenerics::InvalidPathSeparator); - } - } - '<' => { - segment.push(chr); - - match path.next() { - Some('<') => { - return Err(MalformedGenerics::TooManyAngleBrackets); - } - Some('>') => { - return Err(MalformedGenerics::EmptyAngleBrackets); - } - Some(chr) => { - segment.push(chr); - - while let Some(chr) = path.next_if(|c| *c != '>') { - segment.push(chr); - } - } - None => break, - } - } - _ => segment.push(chr), - } - trace!("raw segment: {:?}", segment); - } - - if !segment.is_empty() { - let stripped_segment = strip_generics_from_path_segment(segment)?; - if !stripped_segment.is_empty() { - stripped_segments.push(stripped_segment); - } - } - - debug!("path_str: {:?}\nstripped segments: {:?}", path_str, &stripped_segments); - - let stripped_path = stripped_segments.join("::"); - - if !stripped_path.is_empty() { Ok(stripped_path) } else { Err(MalformedGenerics::MissingType) } -} - -fn strip_generics_from_path_segment(segment: Vec) -> Result { - let mut stripped_segment = String::new(); - let mut param_depth = 0; - - let mut latest_generics_chunk = String::new(); - - for c in segment { - if c == '<' { - param_depth += 1; - latest_generics_chunk.clear(); - } else if c == '>' { - param_depth -= 1; - 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(MalformedGenerics::HasFullyQualifiedSyntax); - } - } else { - if param_depth == 0 { - stripped_segment.push(c); - } else { - latest_generics_chunk.push(c); - } - } - } - - if param_depth == 0 { - Ok(stripped_segment) - } else { - // The segment has unbalanced angle brackets, e.g. `Vec>` - Err(MalformedGenerics::UnbalancedAngleBrackets) - } -} diff --git a/src/librustdoc/passes/collect_intra_doc_links/early.rs b/src/librustdoc/passes/collect_intra_doc_links/early.rs index f690c49005d9c..75c3380ee9bb9 100644 --- a/src/librustdoc/passes/collect_intra_doc_links/early.rs +++ b/src/librustdoc/passes/collect_intra_doc_links/early.rs @@ -1,357 +1,56 @@ -use crate::clean::Attributes; use crate::core::ResolverCaches; -use crate::passes::collect_intra_doc_links::preprocessed_markdown_links; -use crate::passes::collect_intra_doc_links::{Disambiguator, PreprocessedMarkdownLink}; use crate::visit_lib::early_lib_embargo_visit_item; -use rustc_ast::visit::{self, AssocCtxt, Visitor}; +use rustc_ast::visit::{self, Visitor}; use rustc_ast::{self as ast, ItemKind}; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::def::Namespace::*; -use rustc_hir::def::{DefKind, Namespace, Res}; -use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, CRATE_DEF_ID}; -use rustc_hir::TraitCandidate; -use rustc_middle::ty::{DefIdTree, Visibility}; -use rustc_resolve::{ParentScope, Resolver}; -use rustc_span::symbol::sym; -use rustc_span::{Symbol, SyntaxContext}; - -use std::collections::hash_map::Entry; -use std::mem; +use rustc_hir::def::Res; +use rustc_hir::def_id::{DefId, DefIdSet}; +use rustc_resolve::Resolver; +use rustc_span::Symbol; pub(crate) fn early_resolve_intra_doc_links( resolver: &mut Resolver<'_>, krate: &ast::Crate, - document_private_items: bool, ) -> ResolverCaches { - let parent_scope = - ParentScope::module(resolver.expect_module(CRATE_DEF_ID.to_def_id()), resolver); let mut link_resolver = EarlyDocLinkResolver { resolver, - parent_scope, - visited_mods: Default::default(), - markdown_links: Default::default(), - doc_link_resolutions: Default::default(), - traits_in_scope: Default::default(), all_trait_impls: Default::default(), all_macro_rules: Default::default(), extern_doc_reachable: Default::default(), - local_doc_reachable: Default::default(), - document_private_items, }; - // Overridden `visit_item` below doesn't apply to the crate root, - // so we have to visit its attributes and reexports separately. - link_resolver.resolve_doc_links_local(&krate.attrs); - link_resolver.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id()); visit::walk_crate(&mut link_resolver, krate); - - // FIXME: somehow rustdoc is still missing crates even though we loaded all - // the known necessary crates. Load them all unconditionally until we find a way to fix this. - // DO NOT REMOVE THIS without first testing on the reproducer in - // https://github.com/jyn514/objr/commit/edcee7b8124abf0e4c63873e8422ff81beb11ebb - for (extern_name, _) in - link_resolver.resolver.sess().opts.externs.iter().filter(|(_, entry)| entry.add_prelude) - { - link_resolver.resolver.resolve_rustdoc_path(extern_name, TypeNS, parent_scope); - } - link_resolver.process_extern_impls(); ResolverCaches { - markdown_links: Some(link_resolver.markdown_links), - doc_link_resolutions: link_resolver.doc_link_resolutions, - traits_in_scope: link_resolver.traits_in_scope, + markdown_links: Some(Default::default()), all_trait_impls: Some(link_resolver.all_trait_impls), all_macro_rules: link_resolver.all_macro_rules, extern_doc_reachable: link_resolver.extern_doc_reachable, } } -fn doc_attrs<'a>(attrs: impl Iterator) -> Attributes { - Attributes::from_ast_iter(attrs.map(|attr| (attr, None)), true) -} - struct EarlyDocLinkResolver<'r, 'ra> { resolver: &'r mut Resolver<'ra>, - parent_scope: ParentScope<'ra>, - visited_mods: DefIdSet, - markdown_links: FxHashMap>, - doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option>>, - traits_in_scope: DefIdMap>, all_trait_impls: Vec, all_macro_rules: FxHashMap>, /// This set is used as a seed for `effective_visibilities`, which are then extended by some /// more items using `lib_embargo_visit_item` during doc inlining. extern_doc_reachable: DefIdSet, - /// This is an easily identifiable superset of items added to `effective_visibilities` - /// using `lib_embargo_visit_item` during doc inlining. - /// The union of `(extern,local)_doc_reachable` is therefore a superset of - /// `effective_visibilities` and can be used for pruning extern impls here - /// in early doc link resolution. - local_doc_reachable: DefIdSet, - document_private_items: bool, } impl<'ra> EarlyDocLinkResolver<'_, 'ra> { - fn add_traits_in_scope(&mut self, def_id: DefId) { - // Calls to `traits_in_scope` are expensive, so try to avoid them if only possible. - // Keys in the `traits_in_scope` cache are always module IDs. - if let Entry::Vacant(entry) = self.traits_in_scope.entry(def_id) { - let module = self.resolver.get_nearest_non_block_module(def_id); - let module_id = module.def_id(); - let entry = if module_id == def_id { - Some(entry) - } else if let Entry::Vacant(entry) = self.traits_in_scope.entry(module_id) { - Some(entry) - } else { - None - }; - if let Some(entry) = entry { - entry.insert(self.resolver.traits_in_scope( - None, - &ParentScope::module(module, self.resolver), - SyntaxContext::root(), - None, - )); - } - } - } - - fn is_doc_reachable(&self, def_id: DefId) -> bool { - self.extern_doc_reachable.contains(&def_id) || self.local_doc_reachable.contains(&def_id) - } - - /// Add traits in scope for links in impls collected by the `collect-intra-doc-links` pass. - /// That pass filters impls using type-based information, but we don't yet have such - /// information here, so we just conservatively calculate traits in scope for *all* modules - /// having impls in them. fn process_extern_impls(&mut self) { - // Resolving links in already existing crates may trigger loading of new crates. - let mut start_cnum = 0; - loop { - let crates = Vec::from_iter(self.resolver.cstore().crates_untracked()); - for cnum in &crates[start_cnum..] { - early_lib_embargo_visit_item( - self.resolver, - &mut self.extern_doc_reachable, - cnum.as_def_id(), - true, - ); - } - for &cnum in &crates[start_cnum..] { - let all_trait_impls = - Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum)); - let all_inherent_impls = - Vec::from_iter(self.resolver.cstore().inherent_impls_in_crate_untracked(cnum)); - let all_incoherent_impls = Vec::from_iter( - self.resolver.cstore().incoherent_impls_in_crate_untracked(cnum), - ); - - // Querying traits in scope is expensive so we try to prune the impl lists using - // privacy, private traits and impls from other crates are never documented in - // the current crate, and links in their doc comments are not resolved. - for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls { - if self.is_doc_reachable(trait_def_id) - && simplified_self_ty - .and_then(|ty| ty.def()) - .map_or(true, |ty_def_id| self.is_doc_reachable(ty_def_id)) - { - if self.visited_mods.insert(trait_def_id) { - self.resolve_doc_links_extern_impl(trait_def_id, false); - } - self.resolve_doc_links_extern_impl(impl_def_id, false); - } - self.all_trait_impls.push(impl_def_id); - } - for (ty_def_id, impl_def_id) in all_inherent_impls { - if self.is_doc_reachable(ty_def_id) { - self.resolve_doc_links_extern_impl(impl_def_id, true); - } - } - for impl_def_id in all_incoherent_impls { - self.resolve_doc_links_extern_impl(impl_def_id, true); - } - } - - if crates.len() > start_cnum { - start_cnum = crates.len(); - } else { - break; - } - } - } - - fn resolve_doc_links_extern_impl(&mut self, def_id: DefId, is_inherent: bool) { - self.resolve_doc_links_extern_outer_fixme(def_id, def_id); - let assoc_item_def_ids = Vec::from_iter( - self.resolver.cstore().associated_item_def_ids_untracked(def_id, self.resolver.sess()), - ); - for assoc_def_id in assoc_item_def_ids { - if !is_inherent || self.resolver.cstore().visibility_untracked(assoc_def_id).is_public() - { - self.resolve_doc_links_extern_outer_fixme(assoc_def_id, def_id); - } - } - } - - // FIXME: replace all uses with `resolve_doc_links_extern_outer` to actually resolve links, not - // just add traits in scope. This may be expensive and require benchmarking and optimization. - fn resolve_doc_links_extern_outer_fixme(&mut self, def_id: DefId, scope_id: DefId) { - if !self.resolver.cstore().may_have_doc_links_untracked(def_id) { - return; - } - if let Some(parent_id) = self.resolver.opt_parent(scope_id) { - self.add_traits_in_scope(parent_id); - } - } - - fn resolve_doc_links_extern_outer(&mut self, def_id: DefId, scope_id: DefId) { - if !self.resolver.cstore().may_have_doc_links_untracked(def_id) { - return; - } - let attrs = Vec::from_iter( - self.resolver.cstore().item_attrs_untracked(def_id, self.resolver.sess()), - ); - let parent_scope = ParentScope::module( - self.resolver.get_nearest_non_block_module( - self.resolver.opt_parent(scope_id).unwrap_or(scope_id), - ), - self.resolver, - ); - self.resolve_doc_links(doc_attrs(attrs.iter()), parent_scope); - } - - fn resolve_doc_links_extern_inner(&mut self, def_id: DefId) { - if !self.resolver.cstore().may_have_doc_links_untracked(def_id) { - return; - } - let attrs = Vec::from_iter( - self.resolver.cstore().item_attrs_untracked(def_id, self.resolver.sess()), - ); - let parent_scope = ParentScope::module(self.resolver.expect_module(def_id), self.resolver); - self.resolve_doc_links(doc_attrs(attrs.iter()), parent_scope); - } - - fn resolve_doc_links_local(&mut self, attrs: &[ast::Attribute]) { - if !attrs.iter().any(|attr| attr.may_have_doc_links()) { - return; - } - self.resolve_doc_links(doc_attrs(attrs.iter()), self.parent_scope); - } - - fn resolve_and_cache( - &mut self, - path_str: &str, - ns: Namespace, - parent_scope: &ParentScope<'ra>, - ) -> bool { - // FIXME: This caching may be incorrect in case of multiple `macro_rules` - // items with the same name in the same module. - self.doc_link_resolutions - .entry((Symbol::intern(path_str), ns, parent_scope.module.def_id())) - .or_insert_with_key(|(path, ns, _)| { - self.resolver.resolve_rustdoc_path(path.as_str(), *ns, *parent_scope) - }) - .is_some() - } - - fn resolve_doc_links(&mut self, attrs: Attributes, parent_scope: ParentScope<'ra>) { - let mut need_traits_in_scope = false; - for (doc_module, doc) in attrs.prepare_to_doc_link_resolution() { - assert_eq!(doc_module, None); - let mut tmp_links = mem::take(&mut self.markdown_links); - let links = - tmp_links.entry(doc).or_insert_with_key(|doc| preprocessed_markdown_links(doc)); - for PreprocessedMarkdownLink(pp_link, _) in links { - if let Ok(pinfo) = pp_link { - // The logic here is a conservative approximation for path resolution in - // `resolve_with_disambiguator`. - if let Some(ns) = pinfo.disambiguator.map(Disambiguator::ns) { - if self.resolve_and_cache(&pinfo.path_str, ns, &parent_scope) { - continue; - } - } - - // Resolve all namespaces due to no disambiguator or for diagnostics. - let mut any_resolved = false; - let mut need_assoc = false; - for ns in [TypeNS, ValueNS, MacroNS] { - if self.resolve_and_cache(&pinfo.path_str, ns, &parent_scope) { - any_resolved = true; - } else if ns != MacroNS { - need_assoc = true; - } - } - - // Resolve all prefixes for type-relative resolution or for diagnostics. - if need_assoc || !any_resolved { - let mut path = &pinfo.path_str[..]; - while let Some(idx) = path.rfind("::") { - path = &path[..idx]; - need_traits_in_scope = true; - for ns in [TypeNS, ValueNS, MacroNS] { - self.resolve_and_cache(path, ns, &parent_scope); - } - } - } - } - } - self.markdown_links = tmp_links; - } - - if need_traits_in_scope { - self.add_traits_in_scope(parent_scope.module.def_id()); - } - } - - /// When reexports are inlined, they are replaced with item which they refer to, those items - /// may have links in their doc comments, those links are resolved at the item definition site, - /// so we need to know traits in scope at that definition site. - fn process_module_children_or_reexports(&mut self, module_id: DefId) { - if !self.visited_mods.insert(module_id) { - return; // avoid infinite recursion - } - - for child in self.resolver.module_children_or_reexports(module_id) { - // This condition should give a superset of `denied` from `fn clean_use_statement`. - if child.vis.is_public() - || self.document_private_items - && child.vis != Visibility::Restricted(module_id) - && module_id.is_local() - { - if let Some(def_id) = child.res.opt_def_id() && !def_id.is_local() { - self.local_doc_reachable.insert(def_id); - let scope_id = match child.res { - Res::Def( - DefKind::Variant - | DefKind::AssocTy - | DefKind::AssocFn - | DefKind::AssocConst, - .., - ) => self.resolver.parent(def_id), - _ => def_id, - }; - self.resolve_doc_links_extern_outer(def_id, scope_id); // Outer attribute scope - if let Res::Def(DefKind::Mod, ..) = child.res { - self.resolve_doc_links_extern_inner(def_id); // Inner attribute scope - } - if let Res::Def(DefKind::Mod | DefKind::Enum | DefKind::Trait, ..) = child.res { - self.process_module_children_or_reexports(def_id); - } - if let Res::Def(DefKind::Struct | DefKind::Union | DefKind::Variant, _) = - child.res - { - let field_def_ids = Vec::from_iter( - self.resolver - .cstore() - .associated_item_def_ids_untracked(def_id, self.resolver.sess()), - ); - for field_def_id in field_def_ids { - self.resolve_doc_links_extern_outer(field_def_id, scope_id); - } - } - } + for cnum in self.resolver.cstore().crates_untracked() { + early_lib_embargo_visit_item( + self.resolver, + &mut self.extern_doc_reachable, + cnum.as_def_id(), + true, + ); + for (_, impl_def_id, _) in self.resolver.cstore().trait_impls_in_crate_untracked(cnum) { + self.all_trait_impls.push(impl_def_id); } } } @@ -359,73 +58,16 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> { impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> { fn visit_item(&mut self, item: &ast::Item) { - self.resolve_doc_links_local(&item.attrs); // Outer attribute scope - if let ItemKind::Mod(..) = item.kind { - let module_def_id = self.resolver.local_def_id(item.id).to_def_id(); - let module = self.resolver.expect_module(module_def_id); - let old_module = mem::replace(&mut self.parent_scope.module, module); - let old_macro_rules = self.parent_scope.macro_rules; - self.resolve_doc_links_local(&item.attrs); // Inner attribute scope - self.process_module_children_or_reexports(module_def_id); - visit::walk_item(self, item); - if item - .attrs - .iter() - .all(|attr| !attr.has_name(sym::macro_use) && !attr.has_name(sym::macro_escape)) - { - self.parent_scope.macro_rules = old_macro_rules; + match &item.kind { + ItemKind::Impl(impl_) if impl_.of_trait.is_some() => { + self.all_trait_impls.push(self.resolver.local_def_id(item.id).to_def_id()); } - self.parent_scope.module = old_module; - } else { - match &item.kind { - ItemKind::Impl(box ast::Impl { of_trait: Some(trait_ref), .. }) => { - if let Some(partial_res) = self.resolver.get_partial_res(trait_ref.ref_id) - && let Some(res) = partial_res.full_res() - && let Some(trait_def_id) = res.opt_def_id() - && !trait_def_id.is_local() - && self.visited_mods.insert(trait_def_id) { - self.resolve_doc_links_extern_impl(trait_def_id, false); - } - self.all_trait_impls.push(self.resolver.local_def_id(item.id).to_def_id()); - } - ItemKind::MacroDef(macro_def) if macro_def.macro_rules => { - let (macro_rules_scope, res) = - self.resolver.macro_rules_scope(self.resolver.local_def_id(item.id)); - self.parent_scope.macro_rules = macro_rules_scope; - self.all_macro_rules.insert(item.ident.name, res); - } - _ => {} + ItemKind::MacroDef(macro_def) if macro_def.macro_rules => { + let (_, res) = self.resolver.macro_rules_scope(self.resolver.local_def_id(item.id)); + self.all_macro_rules.insert(item.ident.name, res); } - visit::walk_item(self, item); + _ => {} } + visit::walk_item(self, item); } - - fn visit_assoc_item(&mut self, item: &ast::AssocItem, ctxt: AssocCtxt) { - self.resolve_doc_links_local(&item.attrs); - visit::walk_assoc_item(self, item, ctxt) - } - - fn visit_foreign_item(&mut self, item: &ast::ForeignItem) { - self.resolve_doc_links_local(&item.attrs); - visit::walk_foreign_item(self, item) - } - - fn visit_variant(&mut self, v: &ast::Variant) { - self.resolve_doc_links_local(&v.attrs); - visit::walk_variant(self, v) - } - - fn visit_field_def(&mut self, field: &ast::FieldDef) { - self.resolve_doc_links_local(&field.attrs); - visit::walk_field_def(self, field) - } - - fn visit_block(&mut self, block: &ast::Block) { - let old_macro_rules = self.parent_scope.macro_rules; - visit::walk_block(self, block); - self.parent_scope.macro_rules = old_macro_rules; - } - - // NOTE: if doc-comments are ever allowed on other nodes (e.g. function parameters), - // then this will have to implement other visitor methods too. } diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index 634e70ec97a0d..4b1ff68df502f 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -2,11 +2,12 @@ //! process. use rustc_middle::ty::TyCtxt; +use rustc_resolve::rustdoc::DocFragmentKind; use rustc_span::{InnerSpan, Span, DUMMY_SP}; use std::ops::Range; use self::Condition::*; -use crate::clean::{self, DocFragmentKind}; +use crate::clean; use crate::core::DocContext; mod stripper; diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 8ce19c8b5145b..c4b994af13bfe 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -178,6 +178,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "ppv-lite86", "proc-macro-hack", "proc-macro2", + "pulldown-cmark", "psm", "punycode", "quote", @@ -246,6 +247,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "unic-langid-macros", "unic-langid-macros-impl", "unic-ucd-version", + "unicase", "unicode-ident", "unicode-normalization", "unicode-script",