mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 14:55:26 +00:00
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.
This commit is contained in:
parent
a12d31d5a6
commit
b62b82aef4
@ -4613,6 +4613,7 @@ name = "rustc_resolve"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
"pulldown-cmark 0.9.2",
|
||||||
"rustc_arena",
|
"rustc_arena",
|
||||||
"rustc_ast",
|
"rustc_ast",
|
||||||
"rustc_ast_pretty",
|
"rustc_ast_pretty",
|
||||||
@ -4878,7 +4879,6 @@ dependencies = [
|
|||||||
"itertools",
|
"itertools",
|
||||||
"minifier",
|
"minifier",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pulldown-cmark 0.9.2",
|
|
||||||
"rayon",
|
"rayon",
|
||||||
"regex",
|
"regex",
|
||||||
"rustdoc-json-types",
|
"rustdoc-json-types",
|
||||||
|
@ -486,6 +486,14 @@ impl<HCX> ToStableHashKey<HCX> for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<HCX, T1: ToStableHashKey<HCX>, T2: ToStableHashKey<HCX>> ToStableHashKey<HCX> 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<CTX> HashStable<CTX> for bool {
|
impl<CTX> HashStable<CTX> for bool {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) {
|
fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) {
|
||||||
|
@ -2,6 +2,8 @@ use crate::hir;
|
|||||||
|
|
||||||
use rustc_ast as ast;
|
use rustc_ast as ast;
|
||||||
use rustc_ast::NodeId;
|
use rustc_ast::NodeId;
|
||||||
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
|
use rustc_data_structures::stable_hasher::ToStableHashKey;
|
||||||
use rustc_macros::HashStable_Generic;
|
use rustc_macros::HashStable_Generic;
|
||||||
use rustc_span::def_id::{DefId, LocalDefId};
|
use rustc_span::def_id::{DefId, LocalDefId};
|
||||||
use rustc_span::hygiene::MacroKind;
|
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.
|
/// 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").
|
/// 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 {
|
pub enum Namespace {
|
||||||
/// The type namespace includes `struct`s, `enum`s, `union`s, `trait`s, and `mod`s
|
/// The type namespace includes `struct`s, `enum`s, `union`s, `trait`s, and `mod`s
|
||||||
/// (and, by extension, crates).
|
/// (and, by extension, crates).
|
||||||
@ -499,6 +502,15 @@ impl Namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<CTX: crate::HashStableContext> ToStableHashKey<CTX> for Namespace {
|
||||||
|
type KeyType = Namespace;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn to_stable_hash_key(&self, _: &CTX) -> Namespace {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Just a helper ‒ separate structure for each namespace.
|
/// Just a helper ‒ separate structure for each namespace.
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
pub struct PerNS<T> {
|
pub struct PerNS<T> {
|
||||||
@ -760,3 +772,5 @@ pub enum LifetimeRes {
|
|||||||
/// HACK: This is used to recover the NodeId of an elided lifetime.
|
/// HACK: This is used to recover the NodeId of an elided lifetime.
|
||||||
ElidedAnchor { start: NodeId, end: NodeId },
|
ElidedAnchor { start: NodeId, end: NodeId },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type DocLinkResMap = FxHashMap<(Symbol, Namespace), Option<Res<NodeId>>>;
|
||||||
|
@ -11,7 +11,7 @@ use rustc_data_structures::sync::{Lock, LockGuard, Lrc, OnceCell};
|
|||||||
use rustc_data_structures::unhash::UnhashMap;
|
use rustc_data_structures::unhash::UnhashMap;
|
||||||
use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
|
use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
|
||||||
use rustc_expand::proc_macro::{AttrProcMacro, BangProcMacro, DeriveProcMacro};
|
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::def_id::{CrateNum, DefId, DefIndex, CRATE_DEF_INDEX, LOCAL_CRATE};
|
||||||
use rustc_hir::definitions::{DefKey, DefPath, DefPathData, DefPathHash};
|
use rustc_hir::definitions::{DefKey, DefPath, DefPathData, DefPathHash};
|
||||||
use rustc_hir::diagnostic_items::DiagnosticItems;
|
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<Item = (DefId, DefId)> + '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).
|
/// Decodes all traits in the crate (for rustdoc and rustc diagnostics).
|
||||||
fn get_traits(self) -> impl Iterator<Item = DefId> + 'a {
|
fn get_traits(self) -> impl Iterator<Item = DefId> + 'a {
|
||||||
self.root.traits.decode(self).map(move |index| self.local_def_id(index))
|
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<Item = DefId> + '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] {
|
fn get_incoherent_impls(self, tcx: TyCtxt<'tcx>, simp: SimplifiedType) -> &'tcx [DefId] {
|
||||||
if let Some(impls) = self.cdata.incoherent_impls.get(&simp) {
|
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)))
|
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 {
|
fn get_is_intrinsic(self, index: DefIndex) -> bool {
|
||||||
self.root.tables.is_intrinsic.get(self, index)
|
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<Item = DefId> + '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 {
|
impl CrateMetadata {
|
||||||
|
@ -345,6 +345,10 @@ provide! { tcx, def_id, other, cdata,
|
|||||||
expn_that_defined => { cdata.get_expn_that_defined(def_id.index, tcx.sess) }
|
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) }
|
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) }
|
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) {
|
pub(in crate::rmeta) fn provide(providers: &mut Providers) {
|
||||||
@ -613,36 +617,6 @@ impl CStore {
|
|||||||
self.get_crate_data(cnum).get_trait_impls()
|
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<Item = (DefId, DefId)> + '_ {
|
|
||||||
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<Item = DefId> + '_ {
|
|
||||||
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<Item = DefId> + '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 {
|
pub fn is_doc_hidden_untracked(&self, def_id: DefId) -> bool {
|
||||||
self.get_crate_data(def_id.krate)
|
self.get_crate_data(def_id.krate)
|
||||||
.get_attr_flags(def_id.index)
|
.get_attr_flags(def_id.index)
|
||||||
|
@ -3,7 +3,6 @@ use crate::rmeta::def_path_hash_map::DefPathHashMapRef;
|
|||||||
use crate::rmeta::table::TableBuilder;
|
use crate::rmeta::table::TableBuilder;
|
||||||
use crate::rmeta::*;
|
use crate::rmeta::*;
|
||||||
|
|
||||||
use rustc_ast::util::comments;
|
|
||||||
use rustc_ast::Attribute;
|
use rustc_ast::Attribute;
|
||||||
use rustc_data_structures::fingerprint::Fingerprint;
|
use rustc_data_structures::fingerprint::Fingerprint;
|
||||||
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
|
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
|
||||||
@ -772,7 +771,6 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
|||||||
|
|
||||||
struct AnalyzeAttrState {
|
struct AnalyzeAttrState {
|
||||||
is_exported: bool,
|
is_exported: bool,
|
||||||
may_have_doc_links: bool,
|
|
||||||
is_doc_hidden: bool,
|
is_doc_hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -790,15 +788,12 @@ fn analyze_attr(attr: &Attribute, state: &mut AnalyzeAttrState) -> bool {
|
|||||||
let mut should_encode = false;
|
let mut should_encode = false;
|
||||||
if rustc_feature::is_builtin_only_local(attr.name_or_empty()) {
|
if rustc_feature::is_builtin_only_local(attr.name_or_empty()) {
|
||||||
// Attributes marked local-only don't need to be encoded for downstream crates.
|
// 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
|
// 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
|
// downstream crates if they use `#[doc(inline)]` to copy an item's documentation into
|
||||||
// their own.
|
// their own.
|
||||||
if state.is_exported {
|
if state.is_exported {
|
||||||
should_encode = true;
|
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) {
|
} else if attr.has_name(sym::doc) {
|
||||||
// If this is a `doc` attribute that doesn't have anything except maybe `inline` (as in
|
// 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 tcx = self.tcx;
|
||||||
let mut state = AnalyzeAttrState {
|
let mut state = AnalyzeAttrState {
|
||||||
is_exported: tcx.effective_visibilities(()).is_exported(def_id),
|
is_exported: tcx.effective_visibilities(()).is_exported(def_id),
|
||||||
may_have_doc_links: false,
|
|
||||||
is_doc_hidden: false,
|
is_doc_hidden: false,
|
||||||
};
|
};
|
||||||
let attr_iter = tcx
|
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);
|
record_array!(self.tables.attributes[def_id.to_def_id()] <- attr_iter);
|
||||||
|
|
||||||
let mut attr_flags = AttrFlags::empty();
|
let mut attr_flags = AttrFlags::empty();
|
||||||
if state.may_have_doc_links {
|
|
||||||
attr_flags |= AttrFlags::MAY_HAVE_DOC_LINKS;
|
|
||||||
}
|
|
||||||
if state.is_doc_hidden {
|
if state.is_doc_hidden {
|
||||||
attr_flags |= AttrFlags::IS_DOC_HIDDEN;
|
attr_flags |= AttrFlags::IS_DOC_HIDDEN;
|
||||||
}
|
}
|
||||||
@ -1231,6 +1222,14 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
|||||||
def_id.index
|
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))]
|
#[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);
|
record!(self.tables.lookup_stability[LOCAL_CRATE.as_def_id()] <- stability);
|
||||||
}
|
}
|
||||||
self.encode_deprecation(LOCAL_CRATE.as_def_id());
|
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
|
// Normally, this information is encoded when we walk the items
|
||||||
// defined in this crate. However, we skip doing that for proc-macro crates,
|
// 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) {
|
pub fn provide(providers: &mut Providers) {
|
||||||
*providers = 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| {
|
traits_in_crate: |tcx, cnum| {
|
||||||
assert_eq!(cnum, LOCAL_CRATE);
|
assert_eq!(cnum, LOCAL_CRATE);
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ use rustc_attr as attr;
|
|||||||
use rustc_data_structures::svh::Svh;
|
use rustc_data_structures::svh::Svh;
|
||||||
use rustc_data_structures::sync::MetadataRef;
|
use rustc_data_structures::sync::MetadataRef;
|
||||||
use rustc_hir as hir;
|
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::def_id::{CrateNum, DefId, DefIndex, DefPathHash, StableCrateId};
|
||||||
use rustc_hir::definitions::DefKey;
|
use rustc_hir::definitions::DefKey;
|
||||||
use rustc_hir::lang_items::LangItem;
|
use rustc_hir::lang_items::LangItem;
|
||||||
@ -413,6 +413,8 @@ define_tables! {
|
|||||||
module_reexports: Table<DefIndex, LazyArray<ModChild>>,
|
module_reexports: Table<DefIndex, LazyArray<ModChild>>,
|
||||||
deduced_param_attrs: Table<DefIndex, LazyArray<DeducedParamAttrs>>,
|
deduced_param_attrs: Table<DefIndex, LazyArray<DeducedParamAttrs>>,
|
||||||
trait_impl_trait_tys: Table<DefIndex, LazyValue<FxHashMap<DefId, Ty<'static>>>>,
|
trait_impl_trait_tys: Table<DefIndex, LazyValue<FxHashMap<DefId, Ty<'static>>>>,
|
||||||
|
doc_link_resolutions: Table<DefIndex, LazyValue<DocLinkResMap>>,
|
||||||
|
doc_link_traits_in_scope: Table<DefIndex, LazyArray<DefId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(TyEncodable, TyDecodable)]
|
#[derive(TyEncodable, TyDecodable)]
|
||||||
@ -426,8 +428,7 @@ struct VariantData {
|
|||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AttrFlags: u8 {
|
pub struct AttrFlags: u8 {
|
||||||
const MAY_HAVE_DOC_LINKS = 1 << 0;
|
const IS_DOC_HIDDEN = 1 << 0;
|
||||||
const IS_DOC_HIDDEN = 1 << 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +113,7 @@ macro_rules! arena_types {
|
|||||||
[decode] trait_impl_trait_tys: rustc_data_structures::fx::FxHashMap<rustc_hir::def_id::DefId, rustc_middle::ty::Ty<'tcx>>,
|
[decode] trait_impl_trait_tys: rustc_data_structures::fx::FxHashMap<rustc_hir::def_id::DefId, rustc_middle::ty::Ty<'tcx>>,
|
||||||
[] bit_set_u32: rustc_index::bit_set::BitSet<u32>,
|
[] bit_set_u32: rustc_index::bit_set::BitSet<u32>,
|
||||||
[] external_constraints: rustc_middle::traits::solve::ExternalConstraintsData<'tcx>,
|
[] external_constraints: rustc_middle::traits::solve::ExternalConstraintsData<'tcx>,
|
||||||
|
[decode] doc_link_resolutions: rustc_hir::def::DocLinkResMap,
|
||||||
]);
|
]);
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2156,4 +2156,16 @@ rustc_queries! {
|
|||||||
desc { |tcx| "deducing parameter attributes for {}", tcx.def_path_str(def_id) }
|
desc { |tcx| "deducing parameter attributes for {}", tcx.def_path_str(def_id) }
|
||||||
separate_provide_extern
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ use rustc_data_structures::intern::Interned;
|
|||||||
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
|
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
|
||||||
use rustc_data_structures::tagged_ptr::CopyTaggedPtr;
|
use rustc_data_structures::tagged_ptr::CopyTaggedPtr;
|
||||||
use rustc_hir as hir;
|
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::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdMap};
|
||||||
use rustc_hir::Node;
|
use rustc_hir::Node;
|
||||||
use rustc_index::vec::IndexVec;
|
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`.
|
/// exist under `std`. For example, wrote `str::from_utf8` instead of `std::str::from_utf8`.
|
||||||
pub confused_type_with_std_module: FxHashMap<Span, Span>,
|
pub confused_type_with_std_module: FxHashMap<Span, Span>,
|
||||||
pub registered_tools: RegisteredTools,
|
pub registered_tools: RegisteredTools,
|
||||||
|
pub doc_link_resolutions: FxHashMap<LocalDefId, DocLinkResMap>,
|
||||||
|
pub doc_link_traits_in_scope: FxHashMap<LocalDefId, Vec<DefId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolutions that should only be used for lowering.
|
/// Resolutions that should only be used for lowering.
|
||||||
|
@ -81,6 +81,8 @@ trivially_parameterized_over_tcx! {
|
|||||||
rustc_hir::IsAsync,
|
rustc_hir::IsAsync,
|
||||||
rustc_hir::LangItem,
|
rustc_hir::LangItem,
|
||||||
rustc_hir::def::DefKind,
|
rustc_hir::def::DefKind,
|
||||||
|
rustc_hir::def::DocLinkResMap,
|
||||||
|
rustc_hir::def_id::DefId,
|
||||||
rustc_hir::def_id::DefIndex,
|
rustc_hir::def_id::DefIndex,
|
||||||
rustc_hir::definitions::DefKey,
|
rustc_hir::definitions::DefKey,
|
||||||
rustc_index::bit_set::BitSet<u32>,
|
rustc_index::bit_set::BitSet<u32>,
|
||||||
|
@ -45,7 +45,7 @@ use rustc_data_structures::sync::Lrc;
|
|||||||
use rustc_data_structures::unord::UnordSet;
|
use rustc_data_structures::unord::UnordSet;
|
||||||
use rustc_errors::ErrorGuaranteed;
|
use rustc_errors::ErrorGuaranteed;
|
||||||
use rustc_hir as hir;
|
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::def_id::{CrateNum, DefId, DefIdMap, DefIdSet, LocalDefId};
|
||||||
use rustc_hir::hir_id::OwnerId;
|
use rustc_hir::hir_id::OwnerId;
|
||||||
use rustc_hir::lang_items::{LangItem, LanguageItems};
|
use rustc_hir::lang_items::{LangItem, LanguageItems};
|
||||||
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.2.1"
|
bitflags = "1.2.1"
|
||||||
|
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||||
rustc_arena = { path = "../rustc_arena" }
|
rustc_arena = { path = "../rustc_arena" }
|
||||||
rustc_ast = { path = "../rustc_ast" }
|
rustc_ast = { path = "../rustc_ast" }
|
||||||
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
|
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
|
||||||
|
@ -95,7 +95,7 @@ impl<'a> Resolver<'a> {
|
|||||||
/// Reachable macros with block module parents exist due to `#[macro_export] macro_rules!`,
|
/// Reachable macros with block module parents exist due to `#[macro_export] macro_rules!`,
|
||||||
/// but they cannot use def-site hygiene, so the assumption holds
|
/// but they cannot use def-site hygiene, so the assumption holds
|
||||||
/// (<https://github.com/rust-lang/rust/pull/77984#issuecomment-712445508>).
|
/// (<https://github.com/rust-lang/rust/pull/77984#issuecomment-712445508>).
|
||||||
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 {
|
loop {
|
||||||
match self.get_module(def_id) {
|
match self.get_module(def_id) {
|
||||||
Some(module) => return module,
|
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")
|
self.get_module(def_id).expect("argument `DefId` is not a module")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
use RibKind::*;
|
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::{Module, ModuleOrUniformRoot, NameBinding, ParentScope, PathResult};
|
||||||
use crate::{ResolutionError, Resolver, Segment, UseError};
|
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::middle::resolve_lifetime::Set1;
|
||||||
use rustc_middle::ty::DefIdTree;
|
use rustc_middle::ty::DefIdTree;
|
||||||
use rustc_middle::{bug, span_bug};
|
use rustc_middle::{bug, span_bug};
|
||||||
|
use rustc_session::config::CrateType;
|
||||||
use rustc_session::lint;
|
use rustc_session::lint;
|
||||||
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
||||||
use rustc_span::{BytePos, Span};
|
use rustc_span::{BytePos, Span, SyntaxContext};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use rustc_span::source_map::{respan, Spanned};
|
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);
|
self.resolve_arm(arm);
|
||||||
}
|
}
|
||||||
fn visit_block(&mut self, block: &'ast Block) {
|
fn visit_block(&mut self, block: &'ast Block) {
|
||||||
|
let old_macro_rules = self.parent_scope.macro_rules;
|
||||||
self.resolve_block(block);
|
self.resolve_block(block);
|
||||||
|
self.parent_scope.macro_rules = old_macro_rules;
|
||||||
}
|
}
|
||||||
fn visit_anon_const(&mut self, constant: &'ast AnonConst) {
|
fn visit_anon_const(&mut self, constant: &'ast AnonConst) {
|
||||||
// We deal with repeat expressions explicitly in `resolve_expr`.
|
// 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) {
|
fn visit_foreign_item(&mut self, foreign_item: &'ast ForeignItem) {
|
||||||
|
self.resolve_doc_links(&foreign_item.attrs);
|
||||||
match foreign_item.kind {
|
match foreign_item.kind {
|
||||||
ForeignItemKind::TyAlias(box TyAlias { ref generics, .. }) => {
|
ForeignItemKind::TyAlias(box TyAlias { ref generics, .. }) => {
|
||||||
self.with_generic_param_rib(
|
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> {
|
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) {
|
fn resolve_item(&mut self, item: &'ast Item) {
|
||||||
|
self.resolve_doc_links(&item.attrs);
|
||||||
|
|
||||||
let name = item.ident.name;
|
let name = item.ident.name;
|
||||||
debug!("(resolving item) resolving {} ({:?})", name, item.kind);
|
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| {
|
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);
|
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);
|
self.future_proof_import(use_tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemKind::ExternCrate(..) | ItemKind::MacroDef(..) => {
|
ItemKind::MacroDef(ref macro_def) => {
|
||||||
// do nothing, these are just around to be encoded
|
// 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);
|
visit::walk_item(self, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ItemKind::ExternCrate(..) => {}
|
||||||
|
|
||||||
ItemKind::MacCall(_) => panic!("unexpanded macro in resolve!"),
|
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 {
|
for item in trait_items {
|
||||||
|
self.resolve_doc_links(&item.attrs);
|
||||||
match &item.kind {
|
match &item.kind {
|
||||||
AssocItemKind::Const(_, ty, default) => {
|
AssocItemKind::Const(_, ty, default) => {
|
||||||
self.visit_ty(ty);
|
self.visit_ty(ty);
|
||||||
@ -2714,6 +2748,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
|
|||||||
seen_trait_items: &mut FxHashMap<DefId, Span>,
|
seen_trait_items: &mut FxHashMap<DefId, Span>,
|
||||||
) {
|
) {
|
||||||
use crate::ResolutionError::*;
|
use crate::ResolutionError::*;
|
||||||
|
self.resolve_doc_links(&item.attrs);
|
||||||
match &item.kind {
|
match &item.kind {
|
||||||
AssocItemKind::Const(_, ty, default) => {
|
AssocItemKind::Const(_, ty, default) => {
|
||||||
debug!("resolve_implementation AssocItemKind::Const");
|
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);
|
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> {
|
struct LifetimeCountVisitor<'a, 'b> {
|
||||||
@ -4162,6 +4277,7 @@ impl<'a> Resolver<'a> {
|
|||||||
pub(crate) fn late_resolve_crate(&mut self, krate: &Crate) {
|
pub(crate) fn late_resolve_crate(&mut self, krate: &Crate) {
|
||||||
visit::walk_crate(&mut LifetimeCountVisitor { r: self }, krate);
|
visit::walk_crate(&mut LifetimeCountVisitor { r: self }, krate);
|
||||||
let mut late_resolution_visitor = LateResolutionVisitor::new(self);
|
let mut late_resolution_visitor = LateResolutionVisitor::new(self);
|
||||||
|
late_resolution_visitor.resolve_doc_links(&krate.attrs);
|
||||||
visit::walk_crate(&mut late_resolution_visitor, krate);
|
visit::walk_crate(&mut late_resolution_visitor, krate);
|
||||||
for (id, span) in late_resolution_visitor.diagnostic_metadata.unused_labels.iter() {
|
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");
|
self.lint_buffer.buffer_lint(lint::builtin::UNUSED_LABELS, *id, *span, "unused label");
|
||||||
|
@ -33,7 +33,7 @@ use rustc_data_structures::sync::{Lrc, RwLock};
|
|||||||
use rustc_errors::{Applicability, DiagnosticBuilder, ErrorGuaranteed};
|
use rustc_errors::{Applicability, DiagnosticBuilder, ErrorGuaranteed};
|
||||||
use rustc_expand::base::{DeriveResolutions, SyntaxExtension, SyntaxExtensionKind};
|
use rustc_expand::base::{DeriveResolutions, SyntaxExtension, SyntaxExtensionKind};
|
||||||
use rustc_hir::def::Namespace::*;
|
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::{CrateNum, DefId, DefIdMap, LocalDefId};
|
||||||
use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
|
use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
|
||||||
use rustc_hir::definitions::{DefPathData, Definitions};
|
use rustc_hir::definitions::{DefPathData, Definitions};
|
||||||
@ -78,6 +78,7 @@ mod ident;
|
|||||||
mod imports;
|
mod imports;
|
||||||
mod late;
|
mod late;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
pub mod rustdoc;
|
||||||
|
|
||||||
enum Weak {
|
enum Weak {
|
||||||
Yes,
|
Yes,
|
||||||
@ -138,17 +139,17 @@ enum ScopeSet<'a> {
|
|||||||
/// This struct is currently used only for early resolution (imports and macros),
|
/// This struct is currently used only for early resolution (imports and macros),
|
||||||
/// but not for late resolution yet.
|
/// but not for late resolution yet.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct ParentScope<'a> {
|
struct ParentScope<'a> {
|
||||||
pub module: Module<'a>,
|
module: Module<'a>,
|
||||||
expansion: LocalExpnId,
|
expansion: LocalExpnId,
|
||||||
pub macro_rules: MacroRulesScopeRef<'a>,
|
macro_rules: MacroRulesScopeRef<'a>,
|
||||||
derives: &'a [ast::Path],
|
derives: &'a [ast::Path],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ParentScope<'a> {
|
impl<'a> ParentScope<'a> {
|
||||||
/// Creates a parent scope with the passed argument used as the module scope component,
|
/// Creates a parent scope with the passed argument used as the module scope component,
|
||||||
/// and other scope components set to default empty values.
|
/// 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 {
|
ParentScope {
|
||||||
module,
|
module,
|
||||||
expansion: LocalExpnId::ROOT,
|
expansion: LocalExpnId::ROOT,
|
||||||
@ -1046,6 +1047,8 @@ pub struct Resolver<'a> {
|
|||||||
lifetime_elision_allowed: FxHashSet<NodeId>,
|
lifetime_elision_allowed: FxHashSet<NodeId>,
|
||||||
|
|
||||||
effective_visibilities: EffectiveVisibilities,
|
effective_visibilities: EffectiveVisibilities,
|
||||||
|
doc_link_resolutions: FxHashMap<LocalDefId, DocLinkResMap>,
|
||||||
|
doc_link_traits_in_scope: FxHashMap<LocalDefId, Vec<DefId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Nothing really interesting here; it just provides memory for the rest of the crate.
|
/// 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(),
|
confused_type_with_std_module: Default::default(),
|
||||||
lifetime_elision_allowed: Default::default(),
|
lifetime_elision_allowed: Default::default(),
|
||||||
effective_visibilities: 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);
|
let root_parent_scope = ParentScope::module(graph_root, &resolver);
|
||||||
@ -1450,6 +1455,8 @@ impl<'a> Resolver<'a> {
|
|||||||
proc_macros,
|
proc_macros,
|
||||||
confused_type_with_std_module,
|
confused_type_with_std_module,
|
||||||
registered_tools: self.registered_tools,
|
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 {
|
let ast_lowering = ty::ResolverAstLowering {
|
||||||
legacy_const_generic_args: self.legacy_const_generic_args,
|
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(),
|
confused_type_with_std_module: self.confused_type_with_std_module.clone(),
|
||||||
registered_tools: self.registered_tools.clone(),
|
registered_tools: self.registered_tools.clone(),
|
||||||
effective_visibilities: self.effective_visibilities.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 {
|
let ast_lowering = ty::ResolverAstLowering {
|
||||||
legacy_const_generic_args: self.legacy_const_generic_args.clone(),
|
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,
|
&mut self,
|
||||||
current_trait: Option<Module<'a>>,
|
current_trait: Option<Module<'a>>,
|
||||||
parent_scope: &ParentScope<'a>,
|
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,
|
/// 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,
|
/// and also it's a private type. Fortunately rustdoc doesn't need to know the error,
|
||||||
/// just that an error occurred.
|
/// just that an error occurred.
|
||||||
pub fn resolve_rustdoc_path(
|
fn resolve_rustdoc_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
path_str: &str,
|
path_str: &str,
|
||||||
ns: Namespace,
|
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<ModChild> {
|
|
||||||
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.
|
/// For rustdoc.
|
||||||
pub fn macro_rules_scope(&self, def_id: LocalDefId) -> (MacroRulesScopeRef<'a>, Res) {
|
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");
|
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<PartialRes> {
|
|
||||||
self.partial_res_map.get(&node_id).copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the span of the given `DefId` if `DefId` is in the local crate.
|
/// Retrieves the span of the given `DefId` if `DefId` is in the local crate.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn opt_span(&self, def_id: DefId) -> Option<Span> {
|
pub fn opt_span(&self, def_id: DefId) -> Option<Span> {
|
||||||
|
@ -568,7 +568,7 @@ impl<'a> Resolver<'a> {
|
|||||||
Ok((ext, res))
|
Ok((ext, res))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_macro_path(
|
pub(crate) fn resolve_macro_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &ast::Path,
|
path: &ast::Path,
|
||||||
kind: Option<MacroKind>,
|
kind: Option<MacroKind>,
|
||||||
|
361
compiler/rustc_resolve/src/rustdoc.rs
Normal file
361
compiler/rustc_resolve/src/rustdoc.rs
Normal file
@ -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<DefId>,
|
||||||
|
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<T` should trigger this, as should `Vec<T>>`.
|
||||||
|
UnbalancedAngleBrackets,
|
||||||
|
/// The generics are not attached to a type.
|
||||||
|
///
|
||||||
|
/// For example, `<T>` 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, `<Vec as IntoIterator>::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:<T>: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<<T>>` 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<Item = (&'a ast::Attribute, Option<DefId>)>,
|
||||||
|
doc_only: bool,
|
||||||
|
) -> (Vec<DocFragment>, 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<Option<DefId>, 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<char>) -> Result<String, MalformedGenerics> {
|
||||||
|
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<T` or `Vec<T>>`
|
||||||
|
Err(MalformedGenerics::UnbalancedAngleBrackets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn strip_generics_from_path(path_str: &str) -> Result<String, MalformedGenerics> {
|
||||||
|
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<String> {
|
||||||
|
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()
|
||||||
|
}
|
@ -12,7 +12,6 @@ askama = { version = "0.11", default-features = false, features = ["config"] }
|
|||||||
itertools = "0.10.1"
|
itertools = "0.10.1"
|
||||||
minifier = "0.2.2"
|
minifier = "0.2.2"
|
||||||
once_cell = "1.10.0"
|
once_cell = "1.10.0"
|
||||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
|
||||||
regex = "1"
|
regex = "1"
|
||||||
rustdoc-json-types = { path = "../rustdoc-json-types" }
|
rustdoc-json-types = { path = "../rustdoc-json-types" }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
@ -5,12 +5,11 @@ use std::path::PathBuf;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::OnceLock as OnceCell;
|
use std::sync::OnceLock as OnceCell;
|
||||||
use std::{cmp, fmt, iter};
|
use std::{fmt, iter};
|
||||||
|
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
use thin_vec::ThinVec;
|
use thin_vec::ThinVec;
|
||||||
|
|
||||||
use rustc_ast::util::comments::beautify_doc_string;
|
|
||||||
use rustc_ast::{self as ast, AttrStyle};
|
use rustc_ast::{self as ast, AttrStyle};
|
||||||
use rustc_attr::{ConstStability, Deprecation, Stability, StabilityLevel};
|
use rustc_attr::{ConstStability, Deprecation, Stability, StabilityLevel};
|
||||||
use rustc_const_eval::const_eval::is_unstable_const_fn;
|
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_index::vec::IndexVec;
|
||||||
use rustc_middle::ty::fast_reject::SimplifiedType;
|
use rustc_middle::ty::fast_reject::SimplifiedType;
|
||||||
use rustc_middle::ty::{self, DefIdTree, TyCtxt, Visibility};
|
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_session::Session;
|
||||||
use rustc_span::hygiene::MacroKind;
|
use rustc_span::hygiene::MacroKind;
|
||||||
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
||||||
@ -1010,58 +1010,6 @@ impl<I: Iterator<Item = ast::NestedMetaItem>> 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<DefId>,
|
|
||||||
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,
|
/// Collapse a collection of [`DocFragment`]s into one string,
|
||||||
/// handling indentation and newlines as needed.
|
/// handling indentation and newlines as needed.
|
||||||
pub(crate) fn collapse_doc_fragments(doc_strings: &[DocFragment]) -> String {
|
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
|
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<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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A link that has not yet been rendered.
|
/// A link that has not yet been rendered.
|
||||||
///
|
///
|
||||||
/// This link will be turned into a rendered link by [`Item::links`].
|
/// This link will be turned into a rendered link by [`Item::links`].
|
||||||
@ -1231,26 +1099,7 @@ impl Attributes {
|
|||||||
attrs: impl Iterator<Item = (&'a ast::Attribute, Option<DefId>)>,
|
attrs: impl Iterator<Item = (&'a ast::Attribute, Option<DefId>)>,
|
||||||
doc_only: bool,
|
doc_only: bool,
|
||||||
) -> Attributes {
|
) -> Attributes {
|
||||||
let mut doc_strings = Vec::new();
|
let (doc_strings, other_attrs) = attrs_to_doc_fragments(attrs, doc_only);
|
||||||
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);
|
|
||||||
|
|
||||||
Attributes { doc_strings, other_attrs }
|
Attributes { doc_strings, other_attrs }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1269,20 +1118,6 @@ impl Attributes {
|
|||||||
if out.is_empty() { None } else { Some(out) }
|
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<Option<DefId>, 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
|
/// Finds all `doc` attributes as NameValues and returns their corresponding values, joined
|
||||||
/// with newlines.
|
/// with newlines.
|
||||||
pub(crate) fn collapsed_doc_value(&self) -> Option<String> {
|
pub(crate) fn collapsed_doc_value(&self) -> Option<String> {
|
||||||
|
@ -2,6 +2,7 @@ use super::*;
|
|||||||
|
|
||||||
use crate::clean::collapse_doc_fragments;
|
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::create_default_session_globals_then;
|
||||||
use rustc_span::source_map::DUMMY_SP;
|
use rustc_span::source_map::DUMMY_SP;
|
||||||
use rustc_span::symbol::Symbol;
|
use rustc_span::symbol::Symbol;
|
||||||
|
@ -5,14 +5,13 @@ use rustc_data_structures::unord::UnordSet;
|
|||||||
use rustc_errors::emitter::{Emitter, EmitterWriter};
|
use rustc_errors::emitter::{Emitter, EmitterWriter};
|
||||||
use rustc_errors::json::JsonEmitter;
|
use rustc_errors::json::JsonEmitter;
|
||||||
use rustc_feature::UnstableFeatures;
|
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::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId};
|
||||||
use rustc_hir::intravisit::{self, Visitor};
|
use rustc_hir::intravisit::{self, Visitor};
|
||||||
use rustc_hir::{HirId, Path, TraitCandidate};
|
use rustc_hir::{HirId, Path};
|
||||||
use rustc_interface::interface;
|
use rustc_interface::interface;
|
||||||
use rustc_middle::hir::nested_filter;
|
use rustc_middle::hir::nested_filter;
|
||||||
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
|
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
|
||||||
use rustc_resolve as resolve;
|
|
||||||
use rustc_session::config::{self, CrateType, ErrorOutputType};
|
use rustc_session::config::{self, CrateType, ErrorOutputType};
|
||||||
use rustc_session::lint;
|
use rustc_session::lint;
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
@ -35,10 +34,6 @@ pub(crate) use rustc_session::config::{Input, Options, UnstableOptions};
|
|||||||
|
|
||||||
pub(crate) struct ResolverCaches {
|
pub(crate) struct ResolverCaches {
|
||||||
pub(crate) markdown_links: Option<FxHashMap<String, Vec<PreprocessedMarkdownLink>>>,
|
pub(crate) markdown_links: Option<FxHashMap<String, Vec<PreprocessedMarkdownLink>>>,
|
||||||
pub(crate) doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<NodeId>>>,
|
|
||||||
/// Traits in scope for a given module.
|
|
||||||
/// See `collect_intra_doc_links::traits_implemented_by` for more details.
|
|
||||||
pub(crate) traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
|
|
||||||
pub(crate) all_trait_impls: Option<Vec<DefId>>,
|
pub(crate) all_trait_impls: Option<Vec<DefId>>,
|
||||||
pub(crate) all_macro_rules: FxHashMap<Symbol, Res<NodeId>>,
|
pub(crate) all_macro_rules: FxHashMap<Symbol, Res<NodeId>>,
|
||||||
pub(crate) extern_doc_reachable: DefIdSet,
|
pub(crate) extern_doc_reachable: DefIdSet,
|
||||||
@ -46,12 +41,6 @@ pub(crate) struct ResolverCaches {
|
|||||||
|
|
||||||
pub(crate) struct DocContext<'tcx> {
|
pub(crate) struct DocContext<'tcx> {
|
||||||
pub(crate) tcx: TyCtxt<'tcx>,
|
pub(crate) tcx: TyCtxt<'tcx>,
|
||||||
/// Name resolver. Used for intra-doc links.
|
|
||||||
///
|
|
||||||
/// The `Rc<RefCell<...>>` 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<RefCell<interface::BoxedResolver>>,
|
|
||||||
pub(crate) resolver_caches: ResolverCaches,
|
pub(crate) resolver_caches: ResolverCaches,
|
||||||
/// Used for normalization.
|
/// Used for normalization.
|
||||||
///
|
///
|
||||||
@ -100,13 +89,6 @@ impl<'tcx> DocContext<'tcx> {
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn enter_resolver<F, R>(&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
|
/// Call the closure with the given parameters set as
|
||||||
/// the substitutions for a type alias' RHS.
|
/// the substitutions for a type alias' RHS.
|
||||||
pub(crate) fn enter_alias<F, R>(&mut self, substs: DefIdMap<clean::SubstParam>, f: F) -> R
|
pub(crate) fn enter_alias<F, R>(&mut self, substs: DefIdMap<clean::SubstParam>, f: F) -> R
|
||||||
@ -313,7 +295,6 @@ pub(crate) fn create_config(
|
|||||||
|
|
||||||
pub(crate) fn run_global_ctxt(
|
pub(crate) fn run_global_ctxt(
|
||||||
tcx: TyCtxt<'_>,
|
tcx: TyCtxt<'_>,
|
||||||
resolver: Rc<RefCell<interface::BoxedResolver>>,
|
|
||||||
resolver_caches: ResolverCaches,
|
resolver_caches: ResolverCaches,
|
||||||
show_coverage: bool,
|
show_coverage: bool,
|
||||||
render_options: RenderOptions,
|
render_options: RenderOptions,
|
||||||
@ -348,7 +329,6 @@ pub(crate) fn run_global_ctxt(
|
|||||||
|
|
||||||
let mut ctxt = DocContext {
|
let mut ctxt = DocContext {
|
||||||
tcx,
|
tcx,
|
||||||
resolver,
|
|
||||||
resolver_caches,
|
resolver_caches,
|
||||||
param_env: ParamEnv::empty(),
|
param_env: ParamEnv::empty(),
|
||||||
external_traits: Default::default(),
|
external_traits: Default::default(),
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
use rustc_data_structures::fx::FxHashMap;
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
|
pub(crate) use rustc_resolve::rustdoc::main_body_opts;
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
use rustc_span::{Span, Symbol};
|
use rustc_span::{Span, Symbol};
|
||||||
|
|
||||||
@ -58,15 +59,6 @@ mod tests;
|
|||||||
|
|
||||||
const MAX_HEADER_LEVEL: u32 = 6;
|
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).
|
/// Options for rendering Markdown in summaries (e.g., in search results).
|
||||||
pub(crate) fn summary_opts() -> Options {
|
pub(crate) fn summary_opts() -> Options {
|
||||||
Options::ENABLE_TABLES
|
Options::ENABLE_TABLES
|
||||||
|
@ -31,6 +31,7 @@ extern crate tracing;
|
|||||||
//
|
//
|
||||||
// Dependencies listed in Cargo.toml do not need `extern crate`.
|
// Dependencies listed in Cargo.toml do not need `extern crate`.
|
||||||
|
|
||||||
|
extern crate pulldown_cmark;
|
||||||
extern crate rustc_ast;
|
extern crate rustc_ast;
|
||||||
extern crate rustc_ast_pretty;
|
extern crate rustc_ast_pretty;
|
||||||
extern crate rustc_attr;
|
extern crate rustc_attr;
|
||||||
@ -792,22 +793,13 @@ fn main_args(at_args: &[String]) -> MainResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compiler.enter(|queries| {
|
compiler.enter(|queries| {
|
||||||
// We need to hold on to the complete resolver, so we cause everything to be
|
let resolver_caches = {
|
||||||
// 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 expansion = abort_on_err(queries.expansion(), sess);
|
let expansion = abort_on_err(queries.expansion(), sess);
|
||||||
let (krate, resolver, _) = &*expansion.borrow();
|
let (krate, resolver, _) = &*expansion.borrow();
|
||||||
let resolver_caches = resolver.borrow_mut().access(|resolver| {
|
let resolver_caches = resolver.borrow_mut().access(|resolver| {
|
||||||
collect_intra_doc_links::early_resolve_intra_doc_links(
|
collect_intra_doc_links::early_resolve_intra_doc_links(resolver, krate)
|
||||||
resolver,
|
|
||||||
krate,
|
|
||||||
render_options.document_private,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
(resolver.clone(), resolver_caches)
|
resolver_caches
|
||||||
};
|
};
|
||||||
|
|
||||||
if sess.diagnostic().has_errors_or_lint_errors().is_some() {
|
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", || {
|
let (krate, render_opts, mut cache) = sess.time("run_global_ctxt", || {
|
||||||
core::run_global_ctxt(
|
core::run_global_ctxt(
|
||||||
tcx,
|
tcx,
|
||||||
resolver,
|
|
||||||
resolver_caches,
|
resolver_caches,
|
||||||
show_coverage,
|
show_coverage,
|
||||||
render_options,
|
render_options,
|
||||||
|
@ -15,7 +15,8 @@ use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
|
|||||||
use rustc_hir::Mutability;
|
use rustc_hir::Mutability;
|
||||||
use rustc_middle::ty::{DefIdTree, Ty, TyCtxt};
|
use rustc_middle::ty::{DefIdTree, Ty, TyCtxt};
|
||||||
use rustc_middle::{bug, ty};
|
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_session::lint::Lint;
|
||||||
use rustc_span::hygiene::MacroKind;
|
use rustc_span::hygiene::MacroKind;
|
||||||
use rustc_span::symbol::{sym, Ident, Symbol};
|
use rustc_span::symbol::{sym, Ident, Symbol};
|
||||||
@ -23,7 +24,6 @@ use rustc_span::BytePos;
|
|||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::mem;
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::clean::{self, utils::find_nearest_parent_module};
|
use crate::clean::{self, utils::find_nearest_parent_module};
|
||||||
@ -179,47 +179,6 @@ enum ResolutionFailure<'a> {
|
|||||||
NotResolved(UnresolvedPath<'a>),
|
NotResolved(UnresolvedPath<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
enum MalformedGenerics {
|
|
||||||
/// This link has unbalanced angle brackets.
|
|
||||||
///
|
|
||||||
/// For example, `Vec<T` should trigger this, as should `Vec<T>>`.
|
|
||||||
UnbalancedAngleBrackets,
|
|
||||||
/// The generics are not attached to a type.
|
|
||||||
///
|
|
||||||
/// For example, `<T>` 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, `<Vec as IntoIterator>::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:<T>: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<<T>>` should trigger this.
|
|
||||||
TooManyAngleBrackets,
|
|
||||||
/// The link has empty angle brackets.
|
|
||||||
///
|
|
||||||
/// For example, `Vec<>` should trigger this.
|
|
||||||
EmptyAngleBrackets,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub(crate) enum UrlFragment {
|
pub(crate) enum UrlFragment {
|
||||||
Item(DefId),
|
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.
|
/// 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.
|
/// Associated items will never be resolved by this function.
|
||||||
fn resolve_path(
|
fn resolve_path(
|
||||||
&self,
|
&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. `()`).
|
// Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`).
|
||||||
let result = self
|
let result = self
|
||||||
.cx
|
.cx
|
||||||
.resolver_caches
|
.tcx
|
||||||
.doc_link_resolutions
|
.doc_link_resolutions(module_id)
|
||||||
.get(&(Symbol::intern(path_str), ns, module_id))
|
.get(&(Symbol::intern(path_str), ns))
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| panic!("no resolution for {:?} {:?} {:?}", path_str, ns, module_id))
|
||||||
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())
|
.and_then(|res| res.try_into().ok())
|
||||||
.or_else(|| resolve_primitive(path_str, ns));
|
.or_else(|| resolve_primitive(path_str, ns));
|
||||||
debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
|
debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
|
||||||
@ -779,8 +732,7 @@ fn trait_impls_for<'a>(
|
|||||||
module: DefId,
|
module: DefId,
|
||||||
) -> FxHashSet<(DefId, DefId)> {
|
) -> FxHashSet<(DefId, DefId)> {
|
||||||
let tcx = cx.tcx;
|
let tcx = cx.tcx;
|
||||||
let iter = cx.resolver_caches.traits_in_scope[&module].iter().flat_map(|trait_candidate| {
|
let iter = tcx.doc_link_traits_in_scope(module).iter().flat_map(|&trait_| {
|
||||||
let trait_ = trait_candidate.def_id;
|
|
||||||
trace!("considering explicit impl for trait {:?}", trait_);
|
trace!("considering explicit impl for trait {:?}", trait_);
|
||||||
|
|
||||||
// Look at each trait implementation to see if it's an impl for `did`
|
// 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.
|
// 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
|
// Rather than merging all documentation into one, resolve it one attribute at a time
|
||||||
// so we know which module it came from.
|
// 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) {
|
if !may_have_doc_links(&doc) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -975,16 +927,12 @@ fn preprocess_link(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Strip generics from the path.
|
// Strip generics from the path.
|
||||||
let path_str = if path_str.contains(['<', '>'].as_slice()) {
|
let path_str = match strip_generics_from_path(path_str) {
|
||||||
match strip_generics_from_path(path_str) {
|
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
debug!("link has malformed generics: {}", path_str);
|
debug!("link has malformed generics: {}", path_str);
|
||||||
return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned())));
|
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.
|
// 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<Res> {
|
|||||||
debug!("resolved primitives {:?}", prim);
|
debug!("resolved primitives {:?}", prim);
|
||||||
Some(Res::Primitive(prim))
|
Some(Res::Primitive(prim))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_generics_from_path(path_str: &str) -> Result<String, MalformedGenerics> {
|
|
||||||
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<char>) -> Result<String, MalformedGenerics> {
|
|
||||||
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<T` or `Vec<T>>`
|
|
||||||
Err(MalformedGenerics::UnbalancedAngleBrackets)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,397 +1,69 @@
|
|||||||
use crate::clean::Attributes;
|
|
||||||
use crate::core::ResolverCaches;
|
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 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_ast::{self as ast, ItemKind};
|
||||||
use rustc_data_structures::fx::FxHashMap;
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
use rustc_hir::def::Namespace::*;
|
use rustc_hir::def::Res;
|
||||||
use rustc_hir::def::{DefKind, Namespace, Res};
|
use rustc_hir::def_id::{DefId, DefIdSet};
|
||||||
use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, CRATE_DEF_ID};
|
use rustc_resolve::Resolver;
|
||||||
use rustc_hir::TraitCandidate;
|
use rustc_span::Symbol;
|
||||||
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;
|
|
||||||
|
|
||||||
pub(crate) fn early_resolve_intra_doc_links(
|
pub(crate) fn early_resolve_intra_doc_links(
|
||||||
resolver: &mut Resolver<'_>,
|
resolver: &mut Resolver<'_>,
|
||||||
krate: &ast::Crate,
|
krate: &ast::Crate,
|
||||||
document_private_items: bool,
|
|
||||||
) -> ResolverCaches {
|
) -> ResolverCaches {
|
||||||
let parent_scope =
|
|
||||||
ParentScope::module(resolver.expect_module(CRATE_DEF_ID.to_def_id()), resolver);
|
|
||||||
let mut link_resolver = EarlyDocLinkResolver {
|
let mut link_resolver = EarlyDocLinkResolver {
|
||||||
resolver,
|
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_trait_impls: Default::default(),
|
||||||
all_macro_rules: Default::default(),
|
all_macro_rules: Default::default(),
|
||||||
extern_doc_reachable: 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);
|
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();
|
link_resolver.process_extern_impls();
|
||||||
|
|
||||||
ResolverCaches {
|
ResolverCaches {
|
||||||
markdown_links: Some(link_resolver.markdown_links),
|
markdown_links: Some(Default::default()),
|
||||||
doc_link_resolutions: link_resolver.doc_link_resolutions,
|
|
||||||
traits_in_scope: link_resolver.traits_in_scope,
|
|
||||||
all_trait_impls: Some(link_resolver.all_trait_impls),
|
all_trait_impls: Some(link_resolver.all_trait_impls),
|
||||||
all_macro_rules: link_resolver.all_macro_rules,
|
all_macro_rules: link_resolver.all_macro_rules,
|
||||||
extern_doc_reachable: link_resolver.extern_doc_reachable,
|
extern_doc_reachable: link_resolver.extern_doc_reachable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn doc_attrs<'a>(attrs: impl Iterator<Item = &'a ast::Attribute>) -> Attributes {
|
|
||||||
Attributes::from_ast_iter(attrs.map(|attr| (attr, None)), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EarlyDocLinkResolver<'r, 'ra> {
|
struct EarlyDocLinkResolver<'r, 'ra> {
|
||||||
resolver: &'r mut Resolver<'ra>,
|
resolver: &'r mut Resolver<'ra>,
|
||||||
parent_scope: ParentScope<'ra>,
|
|
||||||
visited_mods: DefIdSet,
|
|
||||||
markdown_links: FxHashMap<String, Vec<PreprocessedMarkdownLink>>,
|
|
||||||
doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<ast::NodeId>>>,
|
|
||||||
traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
|
|
||||||
all_trait_impls: Vec<DefId>,
|
all_trait_impls: Vec<DefId>,
|
||||||
all_macro_rules: FxHashMap<Symbol, Res<ast::NodeId>>,
|
all_macro_rules: FxHashMap<Symbol, Res<ast::NodeId>>,
|
||||||
/// This set is used as a seed for `effective_visibilities`, which are then extended by some
|
/// 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.
|
/// more items using `lib_embargo_visit_item` during doc inlining.
|
||||||
extern_doc_reachable: DefIdSet,
|
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> {
|
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) {
|
fn process_extern_impls(&mut self) {
|
||||||
// Resolving links in already existing crates may trigger loading of new crates.
|
for cnum in self.resolver.cstore().crates_untracked() {
|
||||||
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(
|
early_lib_embargo_visit_item(
|
||||||
self.resolver,
|
self.resolver,
|
||||||
&mut self.extern_doc_reachable,
|
&mut self.extern_doc_reachable,
|
||||||
cnum.as_def_id(),
|
cnum.as_def_id(),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
for (_, impl_def_id, _) in self.resolver.cstore().trait_impls_in_crate_untracked(cnum) {
|
||||||
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);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> {
|
impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> {
|
||||||
fn visit_item(&mut self, item: &ast::Item) {
|
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;
|
|
||||||
}
|
|
||||||
self.parent_scope.module = old_module;
|
|
||||||
} else {
|
|
||||||
match &item.kind {
|
match &item.kind {
|
||||||
ItemKind::Impl(box ast::Impl { of_trait: Some(trait_ref), .. }) => {
|
ItemKind::Impl(impl_) if impl_.of_trait.is_some() => {
|
||||||
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());
|
self.all_trait_impls.push(self.resolver.local_def_id(item.id).to_def_id());
|
||||||
}
|
}
|
||||||
ItemKind::MacroDef(macro_def) if macro_def.macro_rules => {
|
ItemKind::MacroDef(macro_def) if macro_def.macro_rules => {
|
||||||
let (macro_rules_scope, res) =
|
let (_, res) = self.resolver.macro_rules_scope(self.resolver.local_def_id(item.id));
|
||||||
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);
|
self.all_macro_rules.insert(item.ident.name, res);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -399,33 +71,3 @@ impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> {
|
|||||||
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.
|
|
||||||
}
|
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
//! process.
|
//! process.
|
||||||
|
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
|
use rustc_resolve::rustdoc::DocFragmentKind;
|
||||||
use rustc_span::{InnerSpan, Span, DUMMY_SP};
|
use rustc_span::{InnerSpan, Span, DUMMY_SP};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use self::Condition::*;
|
use self::Condition::*;
|
||||||
use crate::clean::{self, DocFragmentKind};
|
use crate::clean;
|
||||||
use crate::core::DocContext;
|
use crate::core::DocContext;
|
||||||
|
|
||||||
mod stripper;
|
mod stripper;
|
||||||
|
@ -178,6 +178,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
|
|||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"proc-macro-hack",
|
"proc-macro-hack",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
"pulldown-cmark",
|
||||||
"psm",
|
"psm",
|
||||||
"punycode",
|
"punycode",
|
||||||
"quote",
|
"quote",
|
||||||
@ -246,6 +247,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
|
|||||||
"unic-langid-macros",
|
"unic-langid-macros",
|
||||||
"unic-langid-macros-impl",
|
"unic-langid-macros-impl",
|
||||||
"unic-ucd-version",
|
"unic-ucd-version",
|
||||||
|
"unicase",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
"unicode-script",
|
"unicode-script",
|
||||||
|
Loading…
Reference in New Issue
Block a user