| use std::fmt::Write; |
| use std::hash::Hash; |
| use std::path::PathBuf; |
| use std::sync::{Arc, OnceLock as OnceCell}; |
| use std::{fmt, iter}; |
| |
| use arrayvec::ArrayVec; |
| use itertools::Either; |
| use rustc_abi::{ExternAbi, VariantIdx}; |
| use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; |
| use rustc_data_structures::thin_vec::ThinVec; |
| use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation, DocAttribute}; |
| use rustc_hir::def::{CtorKind, DefKind, Res}; |
| use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId}; |
| use rustc_hir::lang_items::LangItem; |
| use rustc_hir::{Attribute, BodyId, ConstStability, Mutability, Stability, StableSince, find_attr}; |
| use rustc_index::IndexVec; |
| use rustc_metadata::rendered_const; |
| use rustc_middle::span_bug; |
| use rustc_middle::ty::fast_reject::SimplifiedType; |
| use rustc_middle::ty::{self, TyCtxt, Visibility}; |
| use rustc_resolve::rustdoc::{ |
| DocFragment, add_doc_fragment, attrs_to_doc_fragments, inner_docs, span_of_fragments, |
| }; |
| use rustc_session::Session; |
| use rustc_span::hygiene::MacroKind; |
| use rustc_span::symbol::{Symbol, kw, sym}; |
| use rustc_span::{DUMMY_SP, FileName, Loc, RemapPathScopeComponents}; |
| use tracing::{debug, trace}; |
| use {rustc_ast as ast, rustc_hir as hir}; |
| |
| pub(crate) use self::ItemKind::*; |
| pub(crate) use self::Type::{ |
| Array, BareFunction, BorrowedRef, DynTrait, Generic, ImplTrait, Infer, Primitive, QPath, |
| RawPointer, SelfTy, Slice, Tuple, UnsafeBinder, |
| }; |
| use crate::clean::cfg::Cfg; |
| use crate::clean::clean_middle_path; |
| use crate::clean::inline::{self, print_inlined_const}; |
| use crate::clean::utils::{is_literal_expr, print_evaluated_const}; |
| use crate::core::DocContext; |
| use crate::formats::cache::Cache; |
| use crate::formats::item_type::ItemType; |
| use crate::html::format::HrefInfo; |
| use crate::html::render::Context; |
| use crate::passes::collect_intra_doc_links::UrlFragment; |
| |
| #[cfg(test)] |
| mod tests; |
| |
| pub(crate) type ItemIdSet = FxHashSet<ItemId>; |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] |
| pub(crate) enum ItemId { |
| /// A "normal" item that uses a [`DefId`] for identification. |
| DefId(DefId), |
| /// Identifier that is used for auto traits. |
| Auto { trait_: DefId, for_: DefId }, |
| /// Identifier that is used for blanket implementations. |
| Blanket { impl_id: DefId, for_: DefId }, |
| } |
| |
| impl ItemId { |
| #[inline] |
| pub(crate) fn is_local(self) -> bool { |
| match self { |
| ItemId::Auto { for_: id, .. } |
| | ItemId::Blanket { for_: id, .. } |
| | ItemId::DefId(id) => id.is_local(), |
| } |
| } |
| |
| #[inline] |
| #[track_caller] |
| pub(crate) fn expect_def_id(self) -> DefId { |
| self.as_def_id() |
| .unwrap_or_else(|| panic!("ItemId::expect_def_id: `{self:?}` isn't a DefId")) |
| } |
| |
| #[inline] |
| pub(crate) fn as_def_id(self) -> Option<DefId> { |
| match self { |
| ItemId::DefId(id) => Some(id), |
| _ => None, |
| } |
| } |
| |
| #[inline] |
| pub(crate) fn as_local_def_id(self) -> Option<LocalDefId> { |
| self.as_def_id().and_then(|id| id.as_local()) |
| } |
| |
| #[inline] |
| pub(crate) fn krate(self) -> CrateNum { |
| match self { |
| ItemId::Auto { for_: id, .. } |
| | ItemId::Blanket { for_: id, .. } |
| | ItemId::DefId(id) => id.krate, |
| } |
| } |
| } |
| |
| impl From<DefId> for ItemId { |
| fn from(id: DefId) -> Self { |
| Self::DefId(id) |
| } |
| } |
| |
| /// The crate currently being documented. |
| #[derive(Debug)] |
| pub(crate) struct Crate { |
| pub(crate) module: Item, |
| /// Only here so that they can be filtered through the rustdoc passes. |
| pub(crate) external_traits: Box<FxIndexMap<DefId, Trait>>, |
| } |
| |
| impl Crate { |
| pub(crate) fn name(&self, tcx: TyCtxt<'_>) -> Symbol { |
| ExternalCrate::LOCAL.name(tcx) |
| } |
| |
| pub(crate) fn src(&self, tcx: TyCtxt<'_>) -> FileName { |
| ExternalCrate::LOCAL.src(tcx) |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| pub(crate) struct ExternalCrate { |
| pub(crate) crate_num: CrateNum, |
| } |
| |
| impl ExternalCrate { |
| const LOCAL: Self = Self { crate_num: LOCAL_CRATE }; |
| |
| #[inline] |
| pub(crate) fn def_id(&self) -> DefId { |
| self.crate_num.as_def_id() |
| } |
| |
| pub(crate) fn src(&self, tcx: TyCtxt<'_>) -> FileName { |
| let krate_span = tcx.def_span(self.def_id()); |
| tcx.sess.source_map().span_to_filename(krate_span) |
| } |
| |
| pub(crate) fn name(&self, tcx: TyCtxt<'_>) -> Symbol { |
| tcx.crate_name(self.crate_num) |
| } |
| |
| pub(crate) fn src_root(&self, tcx: TyCtxt<'_>) -> PathBuf { |
| match self.src(tcx) { |
| FileName::Real(ref p) => { |
| match p |
| .local_path() |
| .or(Some(p.path(RemapPathScopeComponents::MACRO))) |
| .unwrap() |
| .parent() |
| { |
| Some(p) => p.to_path_buf(), |
| None => PathBuf::new(), |
| } |
| } |
| _ => PathBuf::new(), |
| } |
| } |
| |
| /// Attempts to find where an external crate is located, given that we're |
| /// rendering into the specified source destination. |
| pub(crate) fn location( |
| &self, |
| extern_url: Option<&str>, |
| extern_url_takes_precedence: bool, |
| dst: &std::path::Path, |
| tcx: TyCtxt<'_>, |
| ) -> ExternalLocation { |
| use ExternalLocation::*; |
| |
| fn to_remote(url: impl ToString) -> ExternalLocation { |
| let mut url = url.to_string(); |
| if !url.ends_with('/') { |
| url.push('/'); |
| } |
| Remote(url) |
| } |
| |
| // See if there's documentation generated into the local directory |
| // WARNING: since rustdoc creates these directories as it generates documentation, this check is only accurate before rendering starts. |
| // Make sure to call `location()` by that time. |
| let local_location = dst.join(self.name(tcx).as_str()); |
| if local_location.is_dir() { |
| return Local; |
| } |
| |
| if extern_url_takes_precedence && let Some(url) = extern_url { |
| return to_remote(url); |
| } |
| |
| // Failing that, see if there's an attribute specifying where to find this |
| // external crate |
| let did = self.crate_num.as_def_id(); |
| tcx.get_all_attrs(did) |
| .iter() |
| .find_map(|a| match a { |
| Attribute::Parsed(AttributeKind::Doc(d)) => d.html_root_url.map(|(url, _)| url), |
| _ => None, |
| }) |
| .map(to_remote) |
| .or_else(|| extern_url.map(to_remote)) // NOTE: only matters if `extern_url_takes_precedence` is false |
| .unwrap_or(Unknown) // Well, at least we tried. |
| } |
| |
| fn mapped_root_modules<T>( |
| &self, |
| tcx: TyCtxt<'_>, |
| f: impl Fn(DefId, TyCtxt<'_>) -> Option<(DefId, T)>, |
| ) -> impl Iterator<Item = (DefId, T)> { |
| let root = self.def_id(); |
| |
| if root.is_local() { |
| Either::Left( |
| tcx.hir_root_module() |
| .item_ids |
| .iter() |
| .filter(move |&&id| matches!(tcx.hir_item(id).kind, hir::ItemKind::Mod(..))) |
| .filter_map(move |&id| f(id.owner_id.into(), tcx)), |
| ) |
| } else { |
| Either::Right( |
| tcx.module_children(root) |
| .iter() |
| .filter_map(|item| { |
| if let Res::Def(DefKind::Mod, did) = item.res { Some(did) } else { None } |
| }) |
| .filter_map(move |did| f(did, tcx)), |
| ) |
| } |
| } |
| |
| pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> impl Iterator<Item = (DefId, Symbol)> { |
| self.retrieve_keywords_or_documented_attributes(tcx, |d| d.keyword.map(|(v, _)| v)) |
| } |
| pub(crate) fn documented_attributes( |
| &self, |
| tcx: TyCtxt<'_>, |
| ) -> impl Iterator<Item = (DefId, Symbol)> { |
| self.retrieve_keywords_or_documented_attributes(tcx, |d| d.attribute.map(|(v, _)| v)) |
| } |
| |
| fn retrieve_keywords_or_documented_attributes<F: Fn(&DocAttribute) -> Option<Symbol>>( |
| &self, |
| tcx: TyCtxt<'_>, |
| callback: F, |
| ) -> impl Iterator<Item = (DefId, Symbol)> { |
| let as_target = move |did: DefId, tcx: TyCtxt<'_>| -> Option<(DefId, Symbol)> { |
| tcx.get_all_attrs(did) |
| .iter() |
| .find_map(|attr| match attr { |
| Attribute::Parsed(AttributeKind::Doc(d)) => callback(d), |
| _ => None, |
| }) |
| .map(|value| (did, value)) |
| }; |
| self.mapped_root_modules(tcx, as_target) |
| } |
| |
| pub(crate) fn primitives( |
| &self, |
| tcx: TyCtxt<'_>, |
| ) -> impl Iterator<Item = (DefId, PrimitiveType)> { |
| // Collect all inner modules which are tagged as implementations of |
| // primitives. |
| // |
| // Note that this loop only searches the top-level items of the crate, |
| // and this is intentional. If we were to search the entire crate for an |
| // item tagged with `#[rustc_doc_primitive]` then we would also have to |
| // search the entirety of external modules for items tagged |
| // `#[rustc_doc_primitive]`, which is a pretty inefficient process (decoding |
| // all that metadata unconditionally). |
| // |
| // In order to keep the metadata load under control, the |
| // `#[rustc_doc_primitive]` feature is explicitly designed to only allow the |
| // primitive tags to show up as the top level items in a crate. |
| // |
| // Also note that this does not attempt to deal with modules tagged |
| // duplicately for the same primitive. This is handled later on when |
| // rendering by delegating everything to a hash map. |
| fn as_primitive(def_id: DefId, tcx: TyCtxt<'_>) -> Option<(DefId, PrimitiveType)> { |
| tcx.get_attrs(def_id, sym::rustc_doc_primitive).next().map(|attr| { |
| let attr_value = attr.value_str().expect("syntax should already be validated"); |
| let Some(prim) = PrimitiveType::from_symbol(attr_value) else { |
| span_bug!( |
| attr.span(), |
| "primitive `{attr_value}` is not a member of `PrimitiveType`" |
| ); |
| }; |
| |
| (def_id, prim) |
| }) |
| } |
| |
| self.mapped_root_modules(tcx, as_primitive) |
| } |
| } |
| |
| /// Indicates where an external crate can be found. |
| #[derive(Debug)] |
| pub(crate) enum ExternalLocation { |
| /// Remote URL root of the external crate |
| Remote(String), |
| /// This external crate can be found in the local doc/ folder |
| Local, |
| /// The external crate could not be found. |
| Unknown, |
| } |
| |
| /// Anything with a source location and set of attributes and, optionally, a |
| /// name. That is, anything that can be documented. This doesn't correspond |
| /// directly to the AST's concept of an item; it's a strict superset. |
| #[derive(Clone)] |
| pub(crate) struct Item { |
| pub(crate) inner: Box<ItemInner>, |
| } |
| |
| // Why does the `Item`/`ItemInner` split exist? `Vec<Item>`s are common, and |
| // without the split `Item` would be a large type (100+ bytes) which results in |
| // lots of wasted space in the unused parts of a `Vec<Item>`. With the split, |
| // `Item` is just 8 bytes, and the wasted space is avoided, at the cost of an |
| // extra allocation per item. This is a performance win. |
| #[derive(Clone)] |
| pub(crate) struct ItemInner { |
| /// The name of this item. |
| /// Optional because not every item has a name, e.g. impls. |
| pub(crate) name: Option<Symbol>, |
| /// Information about this item that is specific to what kind of item it is. |
| /// E.g., struct vs enum vs function. |
| pub(crate) kind: ItemKind, |
| pub(crate) attrs: Attributes, |
| /// The effective stability, filled out by the `propagate-stability` pass. |
| pub(crate) stability: Option<Stability>, |
| pub(crate) item_id: ItemId, |
| /// This is the `LocalDefId` of the `use` statement if the item was inlined. |
| /// The crate metadata doesn't hold this information, so the `use` statement |
| /// always belongs to the current crate. |
| pub(crate) inline_stmt_id: Option<LocalDefId>, |
| pub(crate) cfg: Option<Arc<Cfg>>, |
| } |
| |
| impl std::ops::Deref for Item { |
| type Target = ItemInner; |
| fn deref(&self) -> &ItemInner { |
| &self.inner |
| } |
| } |
| |
| /// NOTE: this does NOT unconditionally print every item, to avoid thousands of lines of logs. |
| /// If you want to see the debug output for attributes and the `kind` as well, use `{:#?}` instead of `{:?}`. |
| impl fmt::Debug for Item { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let alternate = f.alternate(); |
| // hand-picked fields that don't bloat the logs too much |
| let mut fmt = f.debug_struct("Item"); |
| fmt.field("name", &self.name).field("item_id", &self.item_id); |
| // allow printing the full item if someone really wants to |
| if alternate { |
| fmt.field("attrs", &self.attrs).field("kind", &self.kind).field("cfg", &self.cfg); |
| } else { |
| fmt.field("kind", &self.type_()); |
| fmt.field("docs", &self.doc_value()); |
| } |
| fmt.finish() |
| } |
| } |
| |
| pub(crate) fn rustc_span(def_id: DefId, tcx: TyCtxt<'_>) -> Span { |
| Span::new(def_id.as_local().map_or_else( |
| || tcx.def_span(def_id), |
| |local| tcx.hir_span_with_body(tcx.local_def_id_to_hir_id(local)), |
| )) |
| } |
| |
| fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool { |
| let parent = tcx.parent(def_id); |
| match tcx.def_kind(parent) { |
| DefKind::Struct | DefKind::Union => false, |
| DefKind::Variant => true, |
| parent_kind => panic!("unexpected parent kind: {parent_kind:?}"), |
| } |
| } |
| |
| impl Item { |
| /// Returns the effective stability of the item. |
| /// |
| /// This method should only be called after the `propagate-stability` pass has been run. |
| pub(crate) fn stability(&self, tcx: TyCtxt<'_>) -> Option<Stability> { |
| let stability = self.inner.stability; |
| debug_assert!( |
| stability.is_some() |
| || self.def_id().is_none_or(|did| tcx.lookup_stability(did).is_none()), |
| "missing stability for cleaned item: {self:?}", |
| ); |
| stability |
| } |
| |
| pub(crate) fn const_stability(&self, tcx: TyCtxt<'_>) -> Option<ConstStability> { |
| self.def_id().and_then(|did| tcx.lookup_const_stability(did)) |
| } |
| |
| pub(crate) fn deprecation(&self, tcx: TyCtxt<'_>) -> Option<Deprecation> { |
| self.def_id().and_then(|did| tcx.lookup_deprecation(did)).or_else(|| { |
| // `allowed_through_unstable_modules` is a bug-compatibility hack for old rustc |
| // versions; the paths that are exposed through it are "deprecated" because they |
| // were never supposed to work at all. |
| let stab = self.stability(tcx)?; |
| if let rustc_hir::StabilityLevel::Stable { |
| allowed_through_unstable_modules: Some(note), |
| .. |
| } = stab.level |
| { |
| Some(Deprecation { |
| since: DeprecatedSince::Unspecified, |
| note: Some(note), |
| suggestion: None, |
| }) |
| } else { |
| None |
| } |
| }) |
| } |
| |
| pub(crate) fn inner_docs(&self, tcx: TyCtxt<'_>) -> bool { |
| self.item_id.as_def_id().map(|did| inner_docs(tcx.get_all_attrs(did))).unwrap_or(false) |
| } |
| |
| pub(crate) fn span(&self, tcx: TyCtxt<'_>) -> Option<Span> { |
| let kind = match &self.kind { |
| ItemKind::StrippedItem(k) => k, |
| _ => &self.kind, |
| }; |
| match kind { |
| ItemKind::ModuleItem(Module { span, .. }) => Some(*span), |
| ItemKind::ImplItem(box Impl { kind: ImplKind::Auto, .. }) => None, |
| ItemKind::ImplItem(box Impl { kind: ImplKind::Blanket(_), .. }) => { |
| if let ItemId::Blanket { impl_id, .. } = self.item_id { |
| Some(rustc_span(impl_id, tcx)) |
| } else { |
| panic!("blanket impl item has non-blanket ID") |
| } |
| } |
| _ => self.def_id().map(|did| rustc_span(did, tcx)), |
| } |
| } |
| |
| pub(crate) fn attr_span(&self, tcx: TyCtxt<'_>) -> rustc_span::Span { |
| span_of_fragments(&self.attrs.doc_strings) |
| .unwrap_or_else(|| self.span(tcx).map_or(DUMMY_SP, |span| span.inner())) |
| } |
| |
| /// Combine all doc strings into a single value handling indentation and newlines as needed. |
| pub(crate) fn doc_value(&self) -> String { |
| self.attrs.doc_value() |
| } |
| |
| /// Combine all doc strings into a single value handling indentation and newlines as needed. |
| /// Returns `None` is there's no documentation at all, and `Some("")` if there is some |
| /// documentation but it is empty (e.g. `#[doc = ""]`). |
| pub(crate) fn opt_doc_value(&self) -> Option<String> { |
| self.attrs.opt_doc_value() |
| } |
| |
| pub(crate) fn from_def_id_and_parts( |
| def_id: DefId, |
| name: Option<Symbol>, |
| kind: ItemKind, |
| cx: &mut DocContext<'_>, |
| ) -> Item { |
| let hir_attrs = cx.tcx.get_all_attrs(def_id); |
| |
| Self::from_def_id_and_attrs_and_parts( |
| def_id, |
| name, |
| kind, |
| Attributes::from_hir(hir_attrs), |
| None, |
| ) |
| } |
| |
| pub(crate) fn from_def_id_and_attrs_and_parts( |
| def_id: DefId, |
| name: Option<Symbol>, |
| kind: ItemKind, |
| attrs: Attributes, |
| cfg: Option<Arc<Cfg>>, |
| ) -> Item { |
| trace!("name={name:?}, def_id={def_id:?} cfg={cfg:?}"); |
| |
| Item { |
| inner: Box::new(ItemInner { |
| item_id: def_id.into(), |
| kind, |
| attrs, |
| stability: None, |
| name, |
| cfg, |
| inline_stmt_id: None, |
| }), |
| } |
| } |
| |
| /// If the item has doc comments from a reexport, returns the item id of that reexport, |
| /// otherwise returns returns the item id. |
| /// |
| /// This is used as a key for caching intra-doc link resolution, |
| /// to prevent two reexports of the same item from using the same cache. |
| pub(crate) fn item_or_reexport_id(&self) -> ItemId { |
| // added documentation on a reexport is always prepended. |
| self.attrs |
| .doc_strings |
| .first() |
| .map(|x| x.item_id) |
| .flatten() |
| .map(ItemId::from) |
| .unwrap_or(self.item_id) |
| } |
| |
| pub(crate) fn links(&self, cx: &Context<'_>) -> Vec<RenderedLink> { |
| use crate::html::format::{href, link_tooltip}; |
| |
| let Some(links) = cx.cache().intra_doc_links.get(&self.item_or_reexport_id()) else { |
| return vec![]; |
| }; |
| links |
| .iter() |
| .filter_map(|ItemLink { link: s, link_text, page_id: id, fragment }| { |
| debug!(?id); |
| if let Ok(HrefInfo { mut url, .. }) = href(*id, cx) { |
| debug!(?url); |
| match fragment { |
| Some(UrlFragment::Item(def_id)) => { |
| write!(url, "{}", crate::html::format::fragment(*def_id, cx.tcx())) |
| .unwrap(); |
| } |
| Some(UrlFragment::UserWritten(raw)) => { |
| url.push('#'); |
| url.push_str(raw); |
| } |
| None => {} |
| } |
| Some(RenderedLink { |
| original_text: s.clone(), |
| new_text: link_text.clone(), |
| tooltip: link_tooltip(*id, fragment, cx).to_string(), |
| href: url, |
| }) |
| } else { |
| None |
| } |
| }) |
| .collect() |
| } |
| |
| /// Find a list of all link names, without finding their href. |
| /// |
| /// This is used for generating summary text, which does not include |
| /// the link text, but does need to know which `[]`-bracketed names |
| /// are actually links. |
| pub(crate) fn link_names(&self, cache: &Cache) -> Vec<RenderedLink> { |
| let Some(links) = cache.intra_doc_links.get(&self.item_id) else { |
| return vec![]; |
| }; |
| links |
| .iter() |
| .map(|ItemLink { link: s, link_text, .. }| RenderedLink { |
| original_text: s.clone(), |
| new_text: link_text.clone(), |
| href: String::new(), |
| tooltip: String::new(), |
| }) |
| .collect() |
| } |
| |
| pub(crate) fn is_crate(&self) -> bool { |
| self.is_mod() && self.def_id().is_some_and(|did| did.is_crate_root()) |
| } |
| pub(crate) fn is_mod(&self) -> bool { |
| self.type_() == ItemType::Module |
| } |
| pub(crate) fn is_struct(&self) -> bool { |
| self.type_() == ItemType::Struct |
| } |
| pub(crate) fn is_enum(&self) -> bool { |
| self.type_() == ItemType::Enum |
| } |
| pub(crate) fn is_variant(&self) -> bool { |
| self.type_() == ItemType::Variant |
| } |
| pub(crate) fn is_associated_type(&self) -> bool { |
| matches!(self.kind, AssocTypeItem(..) | StrippedItem(box AssocTypeItem(..))) |
| } |
| pub(crate) fn is_required_associated_type(&self) -> bool { |
| matches!(self.kind, RequiredAssocTypeItem(..) | StrippedItem(box RequiredAssocTypeItem(..))) |
| } |
| pub(crate) fn is_associated_const(&self) -> bool { |
| matches!(self.kind, ProvidedAssocConstItem(..) | ImplAssocConstItem(..) | StrippedItem(box (ProvidedAssocConstItem(..) | ImplAssocConstItem(..)))) |
| } |
| pub(crate) fn is_required_associated_const(&self) -> bool { |
| matches!(self.kind, RequiredAssocConstItem(..) | StrippedItem(box RequiredAssocConstItem(..))) |
| } |
| pub(crate) fn is_method(&self) -> bool { |
| self.type_() == ItemType::Method |
| } |
| pub(crate) fn is_ty_method(&self) -> bool { |
| self.type_() == ItemType::TyMethod |
| } |
| pub(crate) fn is_primitive(&self) -> bool { |
| self.type_() == ItemType::Primitive |
| } |
| pub(crate) fn is_union(&self) -> bool { |
| self.type_() == ItemType::Union |
| } |
| pub(crate) fn is_import(&self) -> bool { |
| self.type_() == ItemType::Import |
| } |
| pub(crate) fn is_extern_crate(&self) -> bool { |
| self.type_() == ItemType::ExternCrate |
| } |
| pub(crate) fn is_keyword(&self) -> bool { |
| self.type_() == ItemType::Keyword |
| } |
| pub(crate) fn is_attribute(&self) -> bool { |
| self.type_() == ItemType::Attribute |
| } |
| /// Returns `true` if the item kind is one of the following: |
| /// |
| /// * `ItemType::Primitive` |
| /// * `ItemType::Keyword` |
| /// * `ItemType::Attribute` |
| /// |
| /// They are considered fake because they only exist thanks to their |
| /// `#[doc(primitive|keyword|attribute)]` attribute. |
| pub(crate) fn is_fake_item(&self) -> bool { |
| matches!(self.type_(), ItemType::Primitive | ItemType::Keyword | ItemType::Attribute) |
| } |
| pub(crate) fn is_stripped(&self) -> bool { |
| match self.kind { |
| StrippedItem(..) => true, |
| ImportItem(ref i) => !i.should_be_displayed, |
| _ => false, |
| } |
| } |
| pub(crate) fn has_stripped_entries(&self) -> Option<bool> { |
| match self.kind { |
| StructItem(ref struct_) => Some(struct_.has_stripped_entries()), |
| UnionItem(ref union_) => Some(union_.has_stripped_entries()), |
| EnumItem(ref enum_) => Some(enum_.has_stripped_entries()), |
| VariantItem(ref v) => v.has_stripped_entries(), |
| TypeAliasItem(ref type_alias) => { |
| type_alias.inner_type.as_ref().and_then(|t| t.has_stripped_entries()) |
| } |
| _ => None, |
| } |
| } |
| |
| pub(crate) fn stability_class(&self, tcx: TyCtxt<'_>) -> Option<String> { |
| self.stability(tcx).as_ref().and_then(|s| { |
| let mut classes = Vec::with_capacity(2); |
| |
| if s.is_unstable() { |
| classes.push("unstable"); |
| } |
| |
| // FIXME: what about non-staged API items that are deprecated? |
| if self.deprecation(tcx).is_some() { |
| classes.push("deprecated"); |
| } |
| |
| if !classes.is_empty() { Some(classes.join(" ")) } else { None } |
| }) |
| } |
| |
| pub(crate) fn stable_since(&self, tcx: TyCtxt<'_>) -> Option<StableSince> { |
| self.stability(tcx).and_then(|stability| stability.stable_since()) |
| } |
| |
| pub(crate) fn is_non_exhaustive(&self) -> bool { |
| find_attr!(&self.attrs.other_attrs, AttributeKind::NonExhaustive(..)) |
| } |
| |
| /// Returns a documentation-level item type from the item. |
| pub(crate) fn type_(&self) -> ItemType { |
| ItemType::from(self) |
| } |
| |
| pub(crate) fn is_default(&self) -> bool { |
| match self.kind { |
| ItemKind::MethodItem(_, Some(defaultness)) => { |
| defaultness.has_value() && !defaultness.is_final() |
| } |
| _ => false, |
| } |
| } |
| |
| /// Returns a `FnHeader` if `self` is a function item, otherwise returns `None`. |
| pub(crate) fn fn_header(&self, tcx: TyCtxt<'_>) -> Option<hir::FnHeader> { |
| fn build_fn_header( |
| def_id: DefId, |
| tcx: TyCtxt<'_>, |
| asyncness: ty::Asyncness, |
| ) -> hir::FnHeader { |
| let sig = tcx.fn_sig(def_id).skip_binder(); |
| let constness = if tcx.is_const_fn(def_id) { |
| // rustc's `is_const_fn` returns `true` for associated functions that have an `impl const` parent |
| // or that have a `const trait` parent. Do not display those as `const` in rustdoc because we |
| // won't be printing correct syntax plus the syntax is unstable. |
| if let Some(assoc) = tcx.opt_associated_item(def_id) |
| && let ty::AssocContainer::Trait | ty::AssocContainer::TraitImpl(_) = |
| assoc.container |
| { |
| hir::Constness::NotConst |
| } else { |
| hir::Constness::Const |
| } |
| } else { |
| hir::Constness::NotConst |
| }; |
| let asyncness = match asyncness { |
| ty::Asyncness::Yes => hir::IsAsync::Async(DUMMY_SP), |
| ty::Asyncness::No => hir::IsAsync::NotAsync, |
| }; |
| hir::FnHeader { |
| safety: if tcx.codegen_fn_attrs(def_id).safe_target_features { |
| hir::HeaderSafety::SafeTargetFeatures |
| } else { |
| sig.safety().into() |
| }, |
| abi: sig.abi(), |
| constness, |
| asyncness, |
| } |
| } |
| let header = match self.kind { |
| ItemKind::ForeignFunctionItem(_, safety) => { |
| let def_id = self.def_id().unwrap(); |
| let abi = tcx.fn_sig(def_id).skip_binder().abi(); |
| hir::FnHeader { |
| safety: if tcx.codegen_fn_attrs(def_id).safe_target_features { |
| hir::HeaderSafety::SafeTargetFeatures |
| } else { |
| safety.into() |
| }, |
| abi, |
| constness: if tcx.is_const_fn(def_id) { |
| hir::Constness::Const |
| } else { |
| hir::Constness::NotConst |
| }, |
| asyncness: hir::IsAsync::NotAsync, |
| } |
| } |
| ItemKind::FunctionItem(_) |
| | ItemKind::MethodItem(_, _) |
| | ItemKind::RequiredMethodItem(_) => { |
| let def_id = self.def_id().unwrap(); |
| build_fn_header(def_id, tcx, tcx.asyncness(def_id)) |
| } |
| _ => return None, |
| }; |
| Some(header) |
| } |
| |
| /// Returns the visibility of the current item. If the visibility is "inherited", then `None` |
| /// is returned. |
| pub(crate) fn visibility(&self, tcx: TyCtxt<'_>) -> Option<Visibility<DefId>> { |
| let def_id = match self.item_id { |
| // Anything but DefId *shouldn't* matter, but return a reasonable value anyway. |
| ItemId::Auto { .. } | ItemId::Blanket { .. } => return None, |
| ItemId::DefId(def_id) => def_id, |
| }; |
| |
| match self.kind { |
| // Primitives and Keywords are written in the source code as private modules. |
| // The modules need to be private so that nobody actually uses them, but the |
| // keywords and primitives that they are documenting are public. |
| ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => { |
| return Some(Visibility::Public); |
| } |
| // Variant fields inherit their enum's visibility. |
| StructFieldItem(..) if is_field_vis_inherited(tcx, def_id) => { |
| return None; |
| } |
| // Variants always inherit visibility |
| VariantItem(..) | ImplItem(..) => return None, |
| // Trait items inherit the trait's visibility |
| RequiredAssocConstItem(..) |
| | ProvidedAssocConstItem(..) |
| | ImplAssocConstItem(..) |
| | AssocTypeItem(..) |
| | RequiredAssocTypeItem(..) |
| | RequiredMethodItem(..) |
| | MethodItem(..) => { |
| match tcx.associated_item(def_id).container { |
| // Trait impl items always inherit the impl's visibility -- |
| // we don't want to show `pub`. |
| ty::AssocContainer::Trait | ty::AssocContainer::TraitImpl(_) => { |
| return None; |
| } |
| ty::AssocContainer::InherentImpl => {} |
| } |
| } |
| _ => {} |
| } |
| let def_id = match self.inline_stmt_id { |
| Some(inlined) => inlined.to_def_id(), |
| None => def_id, |
| }; |
| Some(tcx.visibility(def_id)) |
| } |
| |
| pub fn is_doc_hidden(&self) -> bool { |
| self.attrs.is_doc_hidden() |
| } |
| |
| pub fn def_id(&self) -> Option<DefId> { |
| self.item_id.as_def_id() |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) enum ItemKind { |
| ExternCrateItem { |
| /// The crate's name, *not* the name it's imported as. |
| src: Option<Symbol>, |
| }, |
| ImportItem(Import), |
| StructItem(Struct), |
| UnionItem(Union), |
| EnumItem(Enum), |
| FunctionItem(Box<Function>), |
| ModuleItem(Module), |
| TypeAliasItem(Box<TypeAlias>), |
| StaticItem(Static), |
| TraitItem(Box<Trait>), |
| TraitAliasItem(TraitAlias), |
| ImplItem(Box<Impl>), |
| /// A required method in a trait declaration meaning it's only a function signature. |
| RequiredMethodItem(Box<Function>), |
| /// A method in a trait impl or a provided method in a trait declaration. |
| /// |
| /// Compared to [RequiredMethodItem], it also contains a method body. |
| MethodItem(Box<Function>, Option<hir::Defaultness>), |
| StructFieldItem(Type), |
| VariantItem(Variant), |
| /// `fn`s from an extern block |
| ForeignFunctionItem(Box<Function>, hir::Safety), |
| /// `static`s from an extern block |
| ForeignStaticItem(Static, hir::Safety), |
| /// `type`s from an extern block |
| ForeignTypeItem, |
| MacroItem(Macro), |
| ProcMacroItem(ProcMacro), |
| PrimitiveItem(PrimitiveType), |
| /// A required associated constant in a trait declaration. |
| RequiredAssocConstItem(Generics, Box<Type>), |
| ConstantItem(Box<Constant>), |
| /// An associated constant in a trait declaration with provided default value. |
| ProvidedAssocConstItem(Box<Constant>), |
| /// An associated constant in an inherent impl or trait impl. |
| ImplAssocConstItem(Box<Constant>), |
| /// A required associated type in a trait declaration. |
| /// |
| /// The bounds may be non-empty if there is a `where` clause. |
| RequiredAssocTypeItem(Generics, Vec<GenericBound>), |
| /// An associated type in a trait impl or a provided one in a trait declaration. |
| AssocTypeItem(Box<TypeAlias>, Vec<GenericBound>), |
| /// An item that has been stripped by a rustdoc pass |
| StrippedItem(Box<ItemKind>), |
| /// This item represents a module with a `#[doc(keyword = "...")]` attribute which is used |
| /// to generate documentation for Rust keywords. |
| KeywordItem, |
| /// This item represents a module with a `#[doc(attribute = "...")]` attribute which is used |
| /// to generate documentation for Rust builtin attributes. |
| AttributeItem, |
| } |
| |
| impl ItemKind { |
| /// Some items contain others such as structs (for their fields) and Enums |
| /// (for their variants). This method returns those contained items. |
| pub(crate) fn inner_items(&self) -> impl Iterator<Item = &Item> { |
| match self { |
| StructItem(s) => s.fields.iter(), |
| UnionItem(u) => u.fields.iter(), |
| VariantItem(v) => match &v.kind { |
| VariantKind::CLike => [].iter(), |
| VariantKind::Tuple(t) => t.iter(), |
| VariantKind::Struct(s) => s.fields.iter(), |
| }, |
| EnumItem(e) => e.variants.iter(), |
| TraitItem(t) => t.items.iter(), |
| ImplItem(i) => i.items.iter(), |
| ModuleItem(m) => m.items.iter(), |
| ExternCrateItem { .. } |
| | ImportItem(_) |
| | FunctionItem(_) |
| | TypeAliasItem(_) |
| | StaticItem(_) |
| | ConstantItem(_) |
| | TraitAliasItem(_) |
| | RequiredMethodItem(_) |
| | MethodItem(_, _) |
| | StructFieldItem(_) |
| | ForeignFunctionItem(_, _) |
| | ForeignStaticItem(_, _) |
| | ForeignTypeItem |
| | MacroItem(_) |
| | ProcMacroItem(_) |
| | PrimitiveItem(_) |
| | RequiredAssocConstItem(..) |
| | ProvidedAssocConstItem(..) |
| | ImplAssocConstItem(..) |
| | RequiredAssocTypeItem(..) |
| | AssocTypeItem(..) |
| | StrippedItem(_) |
| | KeywordItem |
| | AttributeItem => [].iter(), |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Module { |
| pub(crate) items: Vec<Item>, |
| pub(crate) span: Span, |
| } |
| |
| /// A link that has not yet been rendered. |
| /// |
| /// This link will be turned into a rendered link by [`Item::links`]. |
| #[derive(Clone, Debug, PartialEq, Eq, Hash)] |
| pub(crate) struct ItemLink { |
| /// The original link written in the markdown |
| pub(crate) link: Box<str>, |
| /// The link text displayed in the HTML. |
| /// |
| /// This may not be the same as `link` if there was a disambiguator |
| /// in an intra-doc link (e.g. \[`fn@f`\]) |
| pub(crate) link_text: Box<str>, |
| /// The `DefId` of the Item whose **HTML Page** contains the item being |
| /// linked to. This will be different to `item_id` on item's that don't |
| /// have their own page, such as struct fields and enum variants. |
| pub(crate) page_id: DefId, |
| /// The url fragment to append to the link |
| pub(crate) fragment: Option<UrlFragment>, |
| } |
| |
| pub struct RenderedLink { |
| /// The text the link was original written as. |
| /// |
| /// This could potentially include disambiguators and backticks. |
| pub(crate) original_text: Box<str>, |
| /// The text to display in the HTML |
| pub(crate) new_text: Box<str>, |
| /// The URL to put in the `href` |
| pub(crate) href: String, |
| /// The tooltip. |
| pub(crate) tooltip: String, |
| } |
| |
| /// The attributes on an [`Item`], including attributes like `#[derive(...)]` and `#[inline]`, |
| /// as well as doc comments. |
| #[derive(Clone, Debug, Default)] |
| pub(crate) struct Attributes { |
| pub(crate) doc_strings: Vec<DocFragment>, |
| pub(crate) other_attrs: ThinVec<hir::Attribute>, |
| } |
| |
| impl Attributes { |
| pub(crate) fn has_doc_flag<F: Fn(&DocAttribute) -> bool>(&self, callback: F) -> bool { |
| self.other_attrs |
| .iter() |
| .any(|a| matches!(a, Attribute::Parsed(AttributeKind::Doc(d)) if callback(d))) |
| } |
| |
| pub(crate) fn is_doc_hidden(&self) -> bool { |
| find_attr!(&self.other_attrs, AttributeKind::Doc(d) if d.hidden.is_some()) |
| } |
| |
| pub(crate) fn from_hir(attrs: &[hir::Attribute]) -> Attributes { |
| Attributes::from_hir_iter(attrs.iter().map(|attr| (attr, None)), false) |
| } |
| |
| pub(crate) fn from_hir_with_additional( |
| attrs: &[hir::Attribute], |
| (additional_attrs, def_id): (&[hir::Attribute], DefId), |
| ) -> Attributes { |
| // Additional documentation should be shown before the original documentation. |
| let attrs1 = additional_attrs.iter().map(|attr| (attr, Some(def_id))); |
| let attrs2 = attrs.iter().map(|attr| (attr, None)); |
| Attributes::from_hir_iter(attrs1.chain(attrs2), false) |
| } |
| |
| pub(crate) fn from_hir_iter<'a>( |
| attrs: impl Iterator<Item = (&'a hir::Attribute, Option<DefId>)>, |
| doc_only: bool, |
| ) -> Attributes { |
| let (doc_strings, other_attrs) = attrs_to_doc_fragments(attrs, doc_only); |
| Attributes { doc_strings, other_attrs } |
| } |
| |
| /// Combine all doc strings into a single value handling indentation and newlines as needed. |
| pub(crate) fn doc_value(&self) -> String { |
| self.opt_doc_value().unwrap_or_default() |
| } |
| |
| /// Combine all doc strings into a single value handling indentation and newlines as needed. |
| /// Returns `None` is there's no documentation at all, and `Some("")` if there is some |
| /// documentation but it is empty (e.g. `#[doc = ""]`). |
| pub(crate) fn opt_doc_value(&self) -> Option<String> { |
| (!self.doc_strings.is_empty()).then(|| { |
| let mut res = String::new(); |
| for frag in &self.doc_strings { |
| add_doc_fragment(&mut res, frag); |
| } |
| res.pop(); |
| res |
| }) |
| } |
| |
| pub(crate) fn get_doc_aliases(&self) -> Box<[Symbol]> { |
| let mut aliases = FxIndexSet::default(); |
| |
| for attr in &self.other_attrs { |
| if let Attribute::Parsed(AttributeKind::Doc(d)) = attr { |
| for (alias, _) in &d.aliases { |
| aliases.insert(*alias); |
| } |
| } |
| } |
| aliases.into_iter().collect::<Vec<_>>().into() |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) enum GenericBound { |
| TraitBound(PolyTrait, hir::TraitBoundModifiers), |
| Outlives(Lifetime), |
| /// `use<'a, T>` precise-capturing bound syntax |
| Use(Vec<PreciseCapturingArg>), |
| } |
| |
| impl GenericBound { |
| pub(crate) fn sized(cx: &mut DocContext<'_>) -> GenericBound { |
| Self::sized_with(cx, hir::TraitBoundModifiers::NONE) |
| } |
| |
| pub(crate) fn maybe_sized(cx: &mut DocContext<'_>) -> GenericBound { |
| Self::sized_with( |
| cx, |
| hir::TraitBoundModifiers { |
| polarity: hir::BoundPolarity::Maybe(DUMMY_SP), |
| constness: hir::BoundConstness::Never, |
| }, |
| ) |
| } |
| |
| fn sized_with(cx: &mut DocContext<'_>, modifiers: hir::TraitBoundModifiers) -> GenericBound { |
| let did = cx.tcx.require_lang_item(LangItem::Sized, DUMMY_SP); |
| let empty = ty::Binder::dummy(ty::GenericArgs::empty()); |
| let path = clean_middle_path(cx, did, false, ThinVec::new(), empty); |
| inline::record_extern_fqn(cx, did, ItemType::Trait); |
| GenericBound::TraitBound(PolyTrait { trait_: path, generic_params: Vec::new() }, modifiers) |
| } |
| |
| pub(crate) fn is_trait_bound(&self) -> bool { |
| matches!(self, Self::TraitBound(..)) |
| } |
| |
| pub(crate) fn is_sized_bound(&self, cx: &DocContext<'_>) -> bool { |
| self.is_bounded_by_lang_item(cx, LangItem::Sized) |
| } |
| |
| pub(crate) fn is_meta_sized_bound(&self, cx: &DocContext<'_>) -> bool { |
| self.is_bounded_by_lang_item(cx, LangItem::MetaSized) |
| } |
| |
| fn is_bounded_by_lang_item(&self, cx: &DocContext<'_>, lang_item: LangItem) -> bool { |
| if let GenericBound::TraitBound( |
| PolyTrait { ref trait_, .. }, |
| rustc_hir::TraitBoundModifiers::NONE, |
| ) = *self |
| && cx.tcx.is_lang_item(trait_.def_id(), lang_item) |
| { |
| return true; |
| } |
| false |
| } |
| |
| pub(crate) fn get_trait_path(&self) -> Option<Path> { |
| if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, _) = *self { |
| Some(trait_.clone()) |
| } else { |
| None |
| } |
| } |
| } |
| |
| #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct Lifetime(pub Symbol); |
| |
| impl Lifetime { |
| pub(crate) fn statik() -> Lifetime { |
| Lifetime(kw::StaticLifetime) |
| } |
| |
| pub(crate) fn elided() -> Lifetime { |
| Lifetime(kw::UnderscoreLifetime) |
| } |
| } |
| |
| #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] |
| pub(crate) enum PreciseCapturingArg { |
| Lifetime(Lifetime), |
| Param(Symbol), |
| } |
| |
| impl PreciseCapturingArg { |
| pub(crate) fn name(self) -> Symbol { |
| match self { |
| PreciseCapturingArg::Lifetime(lt) => lt.0, |
| PreciseCapturingArg::Param(param) => param, |
| } |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Hash, Debug)] |
| pub(crate) enum WherePredicate { |
| BoundPredicate { ty: Type, bounds: Vec<GenericBound>, bound_params: Vec<GenericParamDef> }, |
| RegionPredicate { lifetime: Lifetime, bounds: Vec<GenericBound> }, |
| EqPredicate { lhs: QPathData, rhs: Term }, |
| } |
| |
| impl WherePredicate { |
| pub(crate) fn get_bounds(&self) -> Option<&[GenericBound]> { |
| match self { |
| WherePredicate::BoundPredicate { bounds, .. } => Some(bounds), |
| WherePredicate::RegionPredicate { bounds, .. } => Some(bounds), |
| _ => None, |
| } |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) enum GenericParamDefKind { |
| Lifetime { outlives: ThinVec<Lifetime> }, |
| Type { bounds: ThinVec<GenericBound>, default: Option<Box<Type>>, synthetic: bool }, |
| // Option<Box<String>> makes this type smaller than `Option<String>` would. |
| Const { ty: Box<Type>, default: Option<Box<String>> }, |
| } |
| |
| impl GenericParamDefKind { |
| pub(crate) fn is_type(&self) -> bool { |
| matches!(self, GenericParamDefKind::Type { .. }) |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct GenericParamDef { |
| pub(crate) name: Symbol, |
| pub(crate) def_id: DefId, |
| pub(crate) kind: GenericParamDefKind, |
| } |
| |
| impl GenericParamDef { |
| pub(crate) fn lifetime(def_id: DefId, name: Symbol) -> Self { |
| Self { name, def_id, kind: GenericParamDefKind::Lifetime { outlives: ThinVec::new() } } |
| } |
| |
| pub(crate) fn is_synthetic_param(&self) -> bool { |
| match self.kind { |
| GenericParamDefKind::Lifetime { .. } | GenericParamDefKind::Const { .. } => false, |
| GenericParamDefKind::Type { synthetic, .. } => synthetic, |
| } |
| } |
| |
| pub(crate) fn is_type(&self) -> bool { |
| self.kind.is_type() |
| } |
| |
| pub(crate) fn get_bounds(&self) -> Option<&[GenericBound]> { |
| match self.kind { |
| GenericParamDefKind::Type { ref bounds, .. } => Some(bounds), |
| _ => None, |
| } |
| } |
| } |
| |
| // maybe use a Generic enum and use Vec<Generic>? |
| #[derive(Clone, PartialEq, Eq, Hash, Debug, Default)] |
| pub(crate) struct Generics { |
| pub(crate) params: ThinVec<GenericParamDef>, |
| pub(crate) where_predicates: ThinVec<WherePredicate>, |
| } |
| |
| impl Generics { |
| pub(crate) fn is_empty(&self) -> bool { |
| self.params.is_empty() && self.where_predicates.is_empty() |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Function { |
| pub(crate) decl: FnDecl, |
| pub(crate) generics: Generics, |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct FnDecl { |
| pub(crate) inputs: Vec<Parameter>, |
| pub(crate) output: Type, |
| pub(crate) c_variadic: bool, |
| } |
| |
| impl FnDecl { |
| pub(crate) fn receiver_type(&self) -> Option<&Type> { |
| self.inputs.first().and_then(|v| v.to_receiver()) |
| } |
| } |
| |
| /// A function parameter. |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct Parameter { |
| pub(crate) name: Option<Symbol>, |
| pub(crate) type_: Type, |
| /// This field is used to represent "const" arguments from the `rustc_legacy_const_generics` |
| /// feature. More information in <https://github.com/rust-lang/rust/issues/83167>. |
| pub(crate) is_const: bool, |
| } |
| |
| impl Parameter { |
| pub(crate) fn to_receiver(&self) -> Option<&Type> { |
| if self.name == Some(kw::SelfLower) { Some(&self.type_) } else { None } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Trait { |
| pub(crate) def_id: DefId, |
| pub(crate) items: Vec<Item>, |
| pub(crate) generics: Generics, |
| pub(crate) bounds: Vec<GenericBound>, |
| } |
| |
| impl Trait { |
| pub(crate) fn is_auto(&self, tcx: TyCtxt<'_>) -> bool { |
| tcx.trait_is_auto(self.def_id) |
| } |
| pub(crate) fn is_notable_trait(&self, tcx: TyCtxt<'_>) -> bool { |
| tcx.is_doc_notable_trait(self.def_id) |
| } |
| pub(crate) fn safety(&self, tcx: TyCtxt<'_>) -> hir::Safety { |
| tcx.trait_def(self.def_id).safety |
| } |
| pub(crate) fn is_dyn_compatible(&self, tcx: TyCtxt<'_>) -> bool { |
| tcx.is_dyn_compatible(self.def_id) |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct TraitAlias { |
| pub(crate) generics: Generics, |
| pub(crate) bounds: Vec<GenericBound>, |
| } |
| |
| /// A trait reference, which may have higher ranked lifetimes. |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct PolyTrait { |
| pub(crate) trait_: Path, |
| pub(crate) generic_params: Vec<GenericParamDef>, |
| } |
| |
| /// Rustdoc's representation of types, mostly based on the [`hir::Ty`]. |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) enum Type { |
| /// A named type, which could be a trait. |
| /// |
| /// This is mostly Rustdoc's version of [`hir::Path`]. |
| /// It has to be different because Rustdoc's [`PathSegment`] can contain cleaned generics. |
| Path { |
| path: Path, |
| }, |
| /// A `dyn Trait` object: `dyn for<'a> Trait<'a> + Send + 'static` |
| DynTrait(Vec<PolyTrait>, Option<Lifetime>), |
| /// A type parameter. |
| Generic(Symbol), |
| /// The `Self` type. |
| SelfTy, |
| /// A primitive (aka, builtin) type. |
| Primitive(PrimitiveType), |
| /// A function pointer: `extern "ABI" fn(...) -> ...` |
| BareFunction(Box<BareFunctionDecl>), |
| /// A tuple type: `(i32, &str)`. |
| Tuple(Vec<Type>), |
| /// A slice type (does *not* include the `&`): `[i32]` |
| Slice(Box<Type>), |
| /// An array type. |
| /// |
| /// The `String` field is a stringified version of the array's length parameter. |
| Array(Box<Type>, Box<str>), |
| Pat(Box<Type>, Box<str>), |
| /// A raw pointer type: `*const i32`, `*mut i32` |
| RawPointer(Mutability, Box<Type>), |
| /// A reference type: `&i32`, `&'a mut Foo` |
| BorrowedRef { |
| lifetime: Option<Lifetime>, |
| mutability: Mutability, |
| type_: Box<Type>, |
| }, |
| |
| /// A qualified path to an associated item: `<Type as Trait>::Name` |
| QPath(Box<QPathData>), |
| |
| /// A type that is inferred: `_` |
| Infer, |
| |
| /// An `impl Trait`: `impl TraitA + TraitB + ...` |
| ImplTrait(Vec<GenericBound>), |
| |
| UnsafeBinder(Box<UnsafeBinderTy>), |
| } |
| |
| impl Type { |
| /// When comparing types for equality, it can help to ignore `&` wrapping. |
| pub(crate) fn without_borrowed_ref(&self) -> &Type { |
| let mut result = self; |
| while let Type::BorrowedRef { type_, .. } = result { |
| result = type_; |
| } |
| result |
| } |
| |
| pub(crate) fn is_borrowed_ref(&self) -> bool { |
| matches!(self, Type::BorrowedRef { .. }) |
| } |
| |
| fn is_type_alias(&self) -> bool { |
| matches!(self, Type::Path { path: Path { res: Res::Def(DefKind::TyAlias, _), .. } }) |
| } |
| |
| /// Check if this type is a subtype of another type for documentation purposes. |
| /// |
| /// This is different from `Eq`, because it knows that things like |
| /// `Infer` and generics have special subtyping rules. |
| /// |
| /// This relation is not commutative when generics are involved: |
| /// |
| /// ```ignore(private) |
| /// # // see types/tests.rs:is_same_generic for the real test |
| /// use rustdoc::format::cache::Cache; |
| /// use rustdoc::clean::types::{Type, PrimitiveType}; |
| /// let cache = Cache::new(false); |
| /// let generic = Type::Generic(rustc_span::symbol::sym::Any); |
| /// let unit = Type::Primitive(PrimitiveType::Unit); |
| /// assert!(!generic.is_doc_subtype_of(&unit, &cache)); |
| /// assert!(unit.is_doc_subtype_of(&generic, &cache)); |
| /// ``` |
| /// |
| /// An owned type is also the same as its borrowed variants (this is commutative), |
| /// but `&T` is not the same as `&mut T`. |
| pub(crate) fn is_doc_subtype_of(&self, other: &Self, cache: &Cache) -> bool { |
| // Strip the references so that it can compare the actual types, unless both are references. |
| // If both are references, leave them alone and compare the mutabilities later. |
| let (self_cleared, other_cleared) = if !self.is_borrowed_ref() || !other.is_borrowed_ref() { |
| (self.without_borrowed_ref(), other.without_borrowed_ref()) |
| } else { |
| (self, other) |
| }; |
| |
| // FIXME: `Cache` does not have the data required to unwrap type aliases, |
| // so we just assume they are equal. |
| // This is only remotely acceptable because we were previously |
| // assuming all types were equal when used |
| // as a generic parameter of a type in `Deref::Target`. |
| if self_cleared.is_type_alias() || other_cleared.is_type_alias() { |
| return true; |
| } |
| |
| match (self_cleared, other_cleared) { |
| // Recursive cases. |
| (Type::Tuple(a), Type::Tuple(b)) => { |
| a.iter().eq_by(b, |a, b| a.is_doc_subtype_of(b, cache)) |
| } |
| (Type::Slice(a), Type::Slice(b)) => a.is_doc_subtype_of(b, cache), |
| (Type::Array(a, al), Type::Array(b, bl)) => al == bl && a.is_doc_subtype_of(b, cache), |
| (Type::RawPointer(mutability, type_), Type::RawPointer(b_mutability, b_type_)) => { |
| mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cache) |
| } |
| ( |
| Type::BorrowedRef { mutability, type_, .. }, |
| Type::BorrowedRef { mutability: b_mutability, type_: b_type_, .. }, |
| ) => mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cache), |
| // Placeholders are equal to all other types. |
| (Type::Infer, _) | (_, Type::Infer) => true, |
| // Generics match everything on the right, but not on the left. |
| // If both sides are generic, this returns true. |
| (_, Type::Generic(_)) => true, |
| (Type::Generic(_), _) => false, |
| // `Self` only matches itself. |
| (Type::SelfTy, Type::SelfTy) => true, |
| // Paths account for both the path itself and its generics. |
| (Type::Path { path: a }, Type::Path { path: b }) => { |
| a.def_id() == b.def_id() |
| && a.generics() |
| .zip(b.generics()) |
| .map(|(ag, bg)| ag.zip(bg).all(|(at, bt)| at.is_doc_subtype_of(bt, cache))) |
| .unwrap_or(true) |
| } |
| // Other cases, such as primitives, just use recursion. |
| (a, b) => a |
| .def_id(cache) |
| .and_then(|a| Some((a, b.def_id(cache)?))) |
| .map(|(a, b)| a == b) |
| .unwrap_or(false), |
| } |
| } |
| |
| pub(crate) fn primitive_type(&self) -> Option<PrimitiveType> { |
| match *self { |
| Primitive(p) | BorrowedRef { type_: box Primitive(p), .. } => Some(p), |
| Slice(..) | BorrowedRef { type_: box Slice(..), .. } => Some(PrimitiveType::Slice), |
| Array(..) | BorrowedRef { type_: box Array(..), .. } => Some(PrimitiveType::Array), |
| Tuple(ref tys) => { |
| if tys.is_empty() { |
| Some(PrimitiveType::Unit) |
| } else { |
| Some(PrimitiveType::Tuple) |
| } |
| } |
| RawPointer(..) => Some(PrimitiveType::RawPointer), |
| BareFunction(..) => Some(PrimitiveType::Fn), |
| _ => None, |
| } |
| } |
| |
| /// Returns the sugared return type for an async function. |
| /// |
| /// For example, if the return type is `impl std::future::Future<Output = i32>`, this function |
| /// will return `i32`. |
| /// |
| /// # Panics |
| /// |
| /// This function will panic if the return type does not match the expected sugaring for async |
| /// functions. |
| pub(crate) fn sugared_async_return_type(self) -> Type { |
| if let Type::ImplTrait(mut v) = self |
| && let Some(GenericBound::TraitBound(PolyTrait { mut trait_, .. }, _)) = v.pop() |
| && let Some(segment) = trait_.segments.pop() |
| && let GenericArgs::AngleBracketed { mut constraints, .. } = segment.args |
| && let Some(constraint) = constraints.pop() |
| && let AssocItemConstraintKind::Equality { term } = constraint.kind |
| && let Term::Type(ty) = term |
| { |
| ty |
| } else { |
| panic!("unexpected async fn return type") |
| } |
| } |
| |
| /// Checks if this is a `T::Name` path for an associated type. |
| pub(crate) fn is_assoc_ty(&self) -> bool { |
| match self { |
| Type::Path { path, .. } => path.is_assoc_ty(), |
| _ => false, |
| } |
| } |
| |
| pub(crate) fn is_self_type(&self) -> bool { |
| matches!(*self, Type::SelfTy) |
| } |
| |
| pub(crate) fn generic_args(&self) -> Option<&GenericArgs> { |
| match self { |
| Type::Path { path, .. } => path.generic_args(), |
| _ => None, |
| } |
| } |
| |
| pub(crate) fn generics(&self) -> Option<impl Iterator<Item = &Type>> { |
| match self { |
| Type::Path { path, .. } => path.generics(), |
| _ => None, |
| } |
| } |
| |
| pub(crate) fn is_full_generic(&self) -> bool { |
| matches!(self, Type::Generic(_)) |
| } |
| |
| pub(crate) fn is_unit(&self) -> bool { |
| matches!(self, Type::Tuple(v) if v.is_empty()) |
| } |
| |
| /// Use this method to get the [DefId] of a [clean] AST node, including [PrimitiveType]s. |
| /// |
| /// [clean]: crate::clean |
| pub(crate) fn def_id(&self, cache: &Cache) -> Option<DefId> { |
| let t: PrimitiveType = match self { |
| Type::Path { path } => return Some(path.def_id()), |
| DynTrait(bounds, _) => return bounds.first().map(|b| b.trait_.def_id()), |
| Primitive(p) => return cache.primitive_locations.get(p).cloned(), |
| BorrowedRef { type_: box Generic(..), .. } => PrimitiveType::Reference, |
| BorrowedRef { type_, .. } => return type_.def_id(cache), |
| Tuple(tys) => { |
| if tys.is_empty() { |
| PrimitiveType::Unit |
| } else { |
| PrimitiveType::Tuple |
| } |
| } |
| BareFunction(..) => PrimitiveType::Fn, |
| Slice(..) => PrimitiveType::Slice, |
| Array(..) => PrimitiveType::Array, |
| Type::Pat(..) => PrimitiveType::Pat, |
| RawPointer(..) => PrimitiveType::RawPointer, |
| QPath(box QPathData { self_type, .. }) => return self_type.def_id(cache), |
| Generic(_) | SelfTy | Infer | ImplTrait(_) | UnsafeBinder(_) => return None, |
| }; |
| Primitive(t).def_id(cache) |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct QPathData { |
| pub assoc: PathSegment, |
| pub self_type: Type, |
| /// FIXME: compute this field on demand. |
| pub should_fully_qualify: bool, |
| pub trait_: Option<Path>, |
| } |
| |
| /// A primitive (aka, builtin) type. |
| /// |
| /// This represents things like `i32`, `str`, etc. |
| /// |
| /// N.B. This has to be different from [`hir::PrimTy`] because it also includes types that aren't |
| /// paths, like [`Self::Unit`]. |
| #[derive(Clone, PartialEq, Eq, Hash, Copy, Debug)] |
| pub(crate) enum PrimitiveType { |
| Isize, |
| I8, |
| I16, |
| I32, |
| I64, |
| I128, |
| Usize, |
| U8, |
| U16, |
| U32, |
| U64, |
| U128, |
| F16, |
| F32, |
| F64, |
| F128, |
| Char, |
| Bool, |
| Str, |
| Slice, |
| Array, |
| Pat, |
| Tuple, |
| Unit, |
| RawPointer, |
| Reference, |
| Fn, |
| Never, |
| } |
| |
| type SimplifiedTypes = FxIndexMap<PrimitiveType, ArrayVec<SimplifiedType, 3>>; |
| impl PrimitiveType { |
| pub(crate) fn from_hir(prim: hir::PrimTy) -> PrimitiveType { |
| use ast::{FloatTy, IntTy, UintTy}; |
| match prim { |
| hir::PrimTy::Int(IntTy::Isize) => PrimitiveType::Isize, |
| hir::PrimTy::Int(IntTy::I8) => PrimitiveType::I8, |
| hir::PrimTy::Int(IntTy::I16) => PrimitiveType::I16, |
| hir::PrimTy::Int(IntTy::I32) => PrimitiveType::I32, |
| hir::PrimTy::Int(IntTy::I64) => PrimitiveType::I64, |
| hir::PrimTy::Int(IntTy::I128) => PrimitiveType::I128, |
| hir::PrimTy::Uint(UintTy::Usize) => PrimitiveType::Usize, |
| hir::PrimTy::Uint(UintTy::U8) => PrimitiveType::U8, |
| hir::PrimTy::Uint(UintTy::U16) => PrimitiveType::U16, |
| hir::PrimTy::Uint(UintTy::U32) => PrimitiveType::U32, |
| hir::PrimTy::Uint(UintTy::U64) => PrimitiveType::U64, |
| hir::PrimTy::Uint(UintTy::U128) => PrimitiveType::U128, |
| hir::PrimTy::Float(FloatTy::F16) => PrimitiveType::F16, |
| hir::PrimTy::Float(FloatTy::F32) => PrimitiveType::F32, |
| hir::PrimTy::Float(FloatTy::F64) => PrimitiveType::F64, |
| hir::PrimTy::Float(FloatTy::F128) => PrimitiveType::F128, |
| hir::PrimTy::Str => PrimitiveType::Str, |
| hir::PrimTy::Bool => PrimitiveType::Bool, |
| hir::PrimTy::Char => PrimitiveType::Char, |
| } |
| } |
| |
| pub(crate) fn from_symbol(s: Symbol) -> Option<PrimitiveType> { |
| match s { |
| sym::isize => Some(PrimitiveType::Isize), |
| sym::i8 => Some(PrimitiveType::I8), |
| sym::i16 => Some(PrimitiveType::I16), |
| sym::i32 => Some(PrimitiveType::I32), |
| sym::i64 => Some(PrimitiveType::I64), |
| sym::i128 => Some(PrimitiveType::I128), |
| sym::usize => Some(PrimitiveType::Usize), |
| sym::u8 => Some(PrimitiveType::U8), |
| sym::u16 => Some(PrimitiveType::U16), |
| sym::u32 => Some(PrimitiveType::U32), |
| sym::u64 => Some(PrimitiveType::U64), |
| sym::u128 => Some(PrimitiveType::U128), |
| sym::bool => Some(PrimitiveType::Bool), |
| sym::char => Some(PrimitiveType::Char), |
| sym::str => Some(PrimitiveType::Str), |
| sym::f16 => Some(PrimitiveType::F16), |
| sym::f32 => Some(PrimitiveType::F32), |
| sym::f64 => Some(PrimitiveType::F64), |
| sym::f128 => Some(PrimitiveType::F128), |
| sym::array => Some(PrimitiveType::Array), |
| sym::slice => Some(PrimitiveType::Slice), |
| sym::tuple => Some(PrimitiveType::Tuple), |
| sym::unit => Some(PrimitiveType::Unit), |
| sym::pointer => Some(PrimitiveType::RawPointer), |
| sym::reference => Some(PrimitiveType::Reference), |
| kw::Fn => Some(PrimitiveType::Fn), |
| sym::never => Some(PrimitiveType::Never), |
| _ => None, |
| } |
| } |
| |
| pub(crate) fn simplified_types() -> &'static SimplifiedTypes { |
| use PrimitiveType::*; |
| use ty::{FloatTy, IntTy, UintTy}; |
| static CELL: OnceCell<SimplifiedTypes> = OnceCell::new(); |
| |
| let single = |x| iter::once(x).collect(); |
| CELL.get_or_init(move || { |
| map! { |
| Isize => single(SimplifiedType::Int(IntTy::Isize)), |
| I8 => single(SimplifiedType::Int(IntTy::I8)), |
| I16 => single(SimplifiedType::Int(IntTy::I16)), |
| I32 => single(SimplifiedType::Int(IntTy::I32)), |
| I64 => single(SimplifiedType::Int(IntTy::I64)), |
| I128 => single(SimplifiedType::Int(IntTy::I128)), |
| Usize => single(SimplifiedType::Uint(UintTy::Usize)), |
| U8 => single(SimplifiedType::Uint(UintTy::U8)), |
| U16 => single(SimplifiedType::Uint(UintTy::U16)), |
| U32 => single(SimplifiedType::Uint(UintTy::U32)), |
| U64 => single(SimplifiedType::Uint(UintTy::U64)), |
| U128 => single(SimplifiedType::Uint(UintTy::U128)), |
| F16 => single(SimplifiedType::Float(FloatTy::F16)), |
| F32 => single(SimplifiedType::Float(FloatTy::F32)), |
| F64 => single(SimplifiedType::Float(FloatTy::F64)), |
| F128 => single(SimplifiedType::Float(FloatTy::F128)), |
| Str => single(SimplifiedType::Str), |
| Bool => single(SimplifiedType::Bool), |
| Char => single(SimplifiedType::Char), |
| Array => single(SimplifiedType::Array), |
| Slice => single(SimplifiedType::Slice), |
| // FIXME: If we ever add an inherent impl for tuples |
| // with different lengths, they won't show in rustdoc. |
| // |
| // Either manually update this arrayvec at this point |
| // or start with a more complex refactoring. |
| Tuple => [SimplifiedType::Tuple(1), SimplifiedType::Tuple(2), SimplifiedType::Tuple(3)].into(), |
| Unit => single(SimplifiedType::Tuple(0)), |
| RawPointer => [SimplifiedType::Ptr(Mutability::Not), SimplifiedType::Ptr(Mutability::Mut)].into_iter().collect(), |
| Reference => [SimplifiedType::Ref(Mutability::Not), SimplifiedType::Ref(Mutability::Mut)].into_iter().collect(), |
| // FIXME: This will be wrong if we ever add inherent impls |
| // for function pointers. |
| Fn => single(SimplifiedType::Function(1)), |
| Never => single(SimplifiedType::Never), |
| } |
| }) |
| } |
| |
| pub(crate) fn impls<'tcx>(&self, tcx: TyCtxt<'tcx>) -> impl Iterator<Item = DefId> + 'tcx { |
| Self::simplified_types() |
| .get(self) |
| .into_iter() |
| .flatten() |
| .flat_map(move |&simp| tcx.incoherent_impls(simp).iter()) |
| .copied() |
| } |
| |
| pub(crate) fn all_impls(tcx: TyCtxt<'_>) -> impl Iterator<Item = DefId> { |
| Self::simplified_types() |
| .values() |
| .flatten() |
| .flat_map(move |&simp| tcx.incoherent_impls(simp).iter()) |
| .copied() |
| } |
| |
| pub(crate) fn as_sym(&self) -> Symbol { |
| use PrimitiveType::*; |
| match self { |
| Isize => sym::isize, |
| I8 => sym::i8, |
| I16 => sym::i16, |
| I32 => sym::i32, |
| I64 => sym::i64, |
| I128 => sym::i128, |
| Usize => sym::usize, |
| U8 => sym::u8, |
| U16 => sym::u16, |
| U32 => sym::u32, |
| U64 => sym::u64, |
| U128 => sym::u128, |
| F16 => sym::f16, |
| F32 => sym::f32, |
| F64 => sym::f64, |
| F128 => sym::f128, |
| Str => sym::str, |
| Bool => sym::bool, |
| Char => sym::char, |
| Array => sym::array, |
| Pat => sym::pat, |
| Slice => sym::slice, |
| Tuple => sym::tuple, |
| Unit => sym::unit, |
| RawPointer => sym::pointer, |
| Reference => sym::reference, |
| Fn => kw::Fn, |
| Never => sym::never, |
| } |
| } |
| |
| /// Returns the DefId of the module with `rustc_doc_primitive` for this primitive type. |
| /// Panics if there is no such module. |
| /// |
| /// This gives precedence to primitives defined in the current crate, and deprioritizes |
| /// primitives defined in `core`, |
| /// but otherwise, if multiple crates define the same primitive, there is no guarantee of which |
| /// will be picked. |
| /// |
| /// In particular, if a crate depends on both `std` and another crate that also defines |
| /// `rustc_doc_primitive`, then it's entirely random whether `std` or the other crate is picked. |
| /// (no_std crates are usually fine unless multiple dependencies define a primitive.) |
| pub(crate) fn primitive_locations(tcx: TyCtxt<'_>) -> &FxIndexMap<PrimitiveType, DefId> { |
| static PRIMITIVE_LOCATIONS: OnceCell<FxIndexMap<PrimitiveType, DefId>> = OnceCell::new(); |
| PRIMITIVE_LOCATIONS.get_or_init(|| { |
| let mut primitive_locations = FxIndexMap::default(); |
| // NOTE: technically this misses crates that are only passed with `--extern` and not loaded when checking the crate. |
| // This is a degenerate case that I don't plan to support. |
| for &crate_num in tcx.crates(()) { |
| let e = ExternalCrate { crate_num }; |
| let crate_name = e.name(tcx); |
| debug!(?crate_num, ?crate_name); |
| for (def_id, prim) in e.primitives(tcx) { |
| // HACK: try to link to std instead where possible |
| if crate_name == sym::core && primitive_locations.contains_key(&prim) { |
| continue; |
| } |
| primitive_locations.insert(prim, def_id); |
| } |
| } |
| let local_primitives = ExternalCrate { crate_num: LOCAL_CRATE }.primitives(tcx); |
| for (def_id, prim) in local_primitives { |
| primitive_locations.insert(prim, def_id); |
| } |
| primitive_locations |
| }) |
| } |
| } |
| |
| impl From<ty::IntTy> for PrimitiveType { |
| fn from(int_ty: ty::IntTy) -> PrimitiveType { |
| match int_ty { |
| ty::IntTy::Isize => PrimitiveType::Isize, |
| ty::IntTy::I8 => PrimitiveType::I8, |
| ty::IntTy::I16 => PrimitiveType::I16, |
| ty::IntTy::I32 => PrimitiveType::I32, |
| ty::IntTy::I64 => PrimitiveType::I64, |
| ty::IntTy::I128 => PrimitiveType::I128, |
| } |
| } |
| } |
| |
| impl From<ty::UintTy> for PrimitiveType { |
| fn from(uint_ty: ty::UintTy) -> PrimitiveType { |
| match uint_ty { |
| ty::UintTy::Usize => PrimitiveType::Usize, |
| ty::UintTy::U8 => PrimitiveType::U8, |
| ty::UintTy::U16 => PrimitiveType::U16, |
| ty::UintTy::U32 => PrimitiveType::U32, |
| ty::UintTy::U64 => PrimitiveType::U64, |
| ty::UintTy::U128 => PrimitiveType::U128, |
| } |
| } |
| } |
| |
| impl From<ty::FloatTy> for PrimitiveType { |
| fn from(float_ty: ty::FloatTy) -> PrimitiveType { |
| match float_ty { |
| ty::FloatTy::F16 => PrimitiveType::F16, |
| ty::FloatTy::F32 => PrimitiveType::F32, |
| ty::FloatTy::F64 => PrimitiveType::F64, |
| ty::FloatTy::F128 => PrimitiveType::F128, |
| } |
| } |
| } |
| |
| impl From<hir::PrimTy> for PrimitiveType { |
| fn from(prim_ty: hir::PrimTy) -> PrimitiveType { |
| match prim_ty { |
| hir::PrimTy::Int(int_ty) => int_ty.into(), |
| hir::PrimTy::Uint(uint_ty) => uint_ty.into(), |
| hir::PrimTy::Float(float_ty) => float_ty.into(), |
| hir::PrimTy::Str => PrimitiveType::Str, |
| hir::PrimTy::Bool => PrimitiveType::Bool, |
| hir::PrimTy::Char => PrimitiveType::Char, |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Struct { |
| pub(crate) ctor_kind: Option<CtorKind>, |
| pub(crate) generics: Generics, |
| pub(crate) fields: ThinVec<Item>, |
| } |
| |
| impl Struct { |
| pub(crate) fn has_stripped_entries(&self) -> bool { |
| self.fields.iter().any(|f| f.is_stripped()) |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Union { |
| pub(crate) generics: Generics, |
| pub(crate) fields: Vec<Item>, |
| } |
| |
| impl Union { |
| pub(crate) fn has_stripped_entries(&self) -> bool { |
| self.fields.iter().any(|f| f.is_stripped()) |
| } |
| } |
| |
| /// This is a more limited form of the standard Struct, different in that |
| /// it lacks the things most items have (name, id, parameterization). Found |
| /// only as a variant in an enum. |
| #[derive(Clone, Debug)] |
| pub(crate) struct VariantStruct { |
| pub(crate) fields: ThinVec<Item>, |
| } |
| |
| impl VariantStruct { |
| pub(crate) fn has_stripped_entries(&self) -> bool { |
| self.fields.iter().any(|f| f.is_stripped()) |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Enum { |
| pub(crate) variants: IndexVec<VariantIdx, Item>, |
| pub(crate) generics: Generics, |
| } |
| |
| impl Enum { |
| pub(crate) fn has_stripped_entries(&self) -> bool { |
| self.variants.iter().any(|f| f.is_stripped()) |
| } |
| |
| pub(crate) fn non_stripped_variants(&self) -> impl Iterator<Item = &Item> { |
| self.variants.iter().filter(|v| !v.is_stripped()) |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Variant { |
| pub kind: VariantKind, |
| pub discriminant: Option<Discriminant>, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) enum VariantKind { |
| CLike, |
| Tuple(ThinVec<Item>), |
| Struct(VariantStruct), |
| } |
| |
| impl Variant { |
| pub(crate) fn has_stripped_entries(&self) -> Option<bool> { |
| match &self.kind { |
| VariantKind::Struct(struct_) => Some(struct_.has_stripped_entries()), |
| VariantKind::CLike | VariantKind::Tuple(_) => None, |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Discriminant { |
| // In the case of cross crate re-exports, we don't have the necessary information |
| // to reconstruct the expression of the discriminant, only the value. |
| pub(super) expr: Option<BodyId>, |
| pub(super) value: DefId, |
| } |
| |
| impl Discriminant { |
| /// Will be `None` in the case of cross-crate reexports, and may be |
| /// simplified |
| pub(crate) fn expr(&self, tcx: TyCtxt<'_>) -> Option<String> { |
| self.expr |
| .map(|body| rendered_const(tcx, tcx.hir_body(body), tcx.hir_body_owner_def_id(body))) |
| } |
| pub(crate) fn value(&self, tcx: TyCtxt<'_>, with_underscores: bool) -> String { |
| print_evaluated_const(tcx, self.value, with_underscores, false).unwrap() |
| } |
| } |
| |
| /// Small wrapper around [`rustc_span::Span`] that adds helper methods |
| /// and enforces calling [`rustc_span::Span::source_callsite()`]. |
| #[derive(Copy, Clone, Debug)] |
| pub(crate) struct Span(rustc_span::Span); |
| |
| impl Span { |
| /// Wraps a [`rustc_span::Span`]. In case this span is the result of a macro expansion, the |
| /// span will be updated to point to the macro invocation instead of the macro definition. |
| /// |
| /// (See rust-lang/rust#39726) |
| pub(crate) fn new(sp: rustc_span::Span) -> Self { |
| Self(sp.source_callsite()) |
| } |
| |
| pub(crate) fn inner(&self) -> rustc_span::Span { |
| self.0 |
| } |
| |
| pub(crate) fn filename(&self, sess: &Session) -> FileName { |
| sess.source_map().span_to_filename(self.0) |
| } |
| |
| pub(crate) fn lo(&self, sess: &Session) -> Loc { |
| sess.source_map().lookup_char_pos(self.0.lo()) |
| } |
| |
| pub(crate) fn hi(&self, sess: &Session) -> Loc { |
| sess.source_map().lookup_char_pos(self.0.hi()) |
| } |
| |
| pub(crate) fn cnum(&self, sess: &Session) -> CrateNum { |
| // FIXME: is there a time when the lo and hi crate would be different? |
| self.lo(sess).file.cnum |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct Path { |
| pub(crate) res: Res, |
| pub(crate) segments: ThinVec<PathSegment>, |
| } |
| |
| impl Path { |
| pub(crate) fn def_id(&self) -> DefId { |
| self.res.def_id() |
| } |
| |
| pub(crate) fn last_opt(&self) -> Option<Symbol> { |
| self.segments.last().map(|s| s.name) |
| } |
| |
| pub(crate) fn last(&self) -> Symbol { |
| self.last_opt().expect("segments were empty") |
| } |
| |
| pub(crate) fn whole_name(&self) -> String { |
| self.segments |
| .iter() |
| .map(|s| if s.name == kw::PathRoot { "" } else { s.name.as_str() }) |
| .intersperse("::") |
| .collect() |
| } |
| |
| /// Checks if this is a `T::Name` path for an associated type. |
| pub(crate) fn is_assoc_ty(&self) -> bool { |
| match self.res { |
| Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::Def(DefKind::TyParam, _) |
| if self.segments.len() != 1 => |
| { |
| true |
| } |
| Res::Def(DefKind::AssocTy, _) => true, |
| _ => false, |
| } |
| } |
| |
| pub(crate) fn generic_args(&self) -> Option<&GenericArgs> { |
| self.segments.last().map(|seg| &seg.args) |
| } |
| |
| pub(crate) fn generics(&self) -> Option<impl Iterator<Item = &Type>> { |
| self.segments.last().and_then(|seg| { |
| if let GenericArgs::AngleBracketed { ref args, .. } = seg.args { |
| Some(args.iter().filter_map(|arg| match arg { |
| GenericArg::Type(ty) => Some(ty), |
| _ => None, |
| })) |
| } else { |
| None |
| } |
| }) |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) enum GenericArg { |
| Lifetime(Lifetime), |
| Type(Type), |
| Const(Box<ConstantKind>), |
| Infer, |
| } |
| |
| impl GenericArg { |
| pub(crate) fn as_lt(&self) -> Option<&Lifetime> { |
| if let Self::Lifetime(lt) = self { Some(lt) } else { None } |
| } |
| |
| pub(crate) fn as_ty(&self) -> Option<&Type> { |
| if let Self::Type(ty) = self { Some(ty) } else { None } |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) enum GenericArgs { |
| /// `<args, constraints = ..>` |
| AngleBracketed { args: ThinVec<GenericArg>, constraints: ThinVec<AssocItemConstraint> }, |
| /// `(inputs) -> output` |
| Parenthesized { inputs: ThinVec<Type>, output: Option<Box<Type>> }, |
| /// `(..)` |
| ReturnTypeNotation, |
| } |
| |
| impl GenericArgs { |
| pub(crate) fn is_empty(&self) -> bool { |
| match self { |
| GenericArgs::AngleBracketed { args, constraints } => { |
| args.is_empty() && constraints.is_empty() |
| } |
| GenericArgs::Parenthesized { inputs, output } => inputs.is_empty() && output.is_none(), |
| GenericArgs::ReturnTypeNotation => false, |
| } |
| } |
| pub(crate) fn constraints(&self) -> Box<dyn Iterator<Item = AssocItemConstraint> + '_> { |
| match self { |
| GenericArgs::AngleBracketed { constraints, .. } => { |
| Box::new(constraints.iter().cloned()) |
| } |
| GenericArgs::Parenthesized { output, .. } => Box::new( |
| output |
| .as_ref() |
| .map(|ty| AssocItemConstraint { |
| assoc: PathSegment { |
| name: sym::Output, |
| args: GenericArgs::AngleBracketed { |
| args: ThinVec::new(), |
| constraints: ThinVec::new(), |
| }, |
| }, |
| kind: AssocItemConstraintKind::Equality { |
| term: Term::Type((**ty).clone()), |
| }, |
| }) |
| .into_iter(), |
| ), |
| GenericArgs::ReturnTypeNotation => Box::new([].into_iter()), |
| } |
| } |
| } |
| |
| impl<'a> IntoIterator for &'a GenericArgs { |
| type IntoIter = Box<dyn Iterator<Item = GenericArg> + 'a>; |
| type Item = GenericArg; |
| fn into_iter(self) -> Self::IntoIter { |
| match self { |
| GenericArgs::AngleBracketed { args, .. } => Box::new(args.iter().cloned()), |
| GenericArgs::Parenthesized { inputs, .. } => { |
| // FIXME: This isn't really right, since `Fn(A, B)` is `Fn<(A, B)>` |
| Box::new(inputs.iter().cloned().map(GenericArg::Type)) |
| } |
| GenericArgs::ReturnTypeNotation => Box::new([].into_iter()), |
| } |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct PathSegment { |
| pub(crate) name: Symbol, |
| pub(crate) args: GenericArgs, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) enum TypeAliasInnerType { |
| Enum { variants: IndexVec<VariantIdx, Item>, is_non_exhaustive: bool }, |
| Union { fields: Vec<Item> }, |
| Struct { ctor_kind: Option<CtorKind>, fields: Vec<Item> }, |
| } |
| |
| impl TypeAliasInnerType { |
| fn has_stripped_entries(&self) -> Option<bool> { |
| Some(match self { |
| Self::Enum { variants, .. } => variants.iter().any(|v| v.is_stripped()), |
| Self::Union { fields } | Self::Struct { fields, .. } => { |
| fields.iter().any(|f| f.is_stripped()) |
| } |
| }) |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct TypeAlias { |
| pub(crate) type_: Type, |
| pub(crate) generics: Generics, |
| /// Inner `AdtDef` type, ie `type TyKind = IrTyKind<Adt, Ty>`, |
| /// to be shown directly on the typedef page. |
| pub(crate) inner_type: Option<TypeAliasInnerType>, |
| /// `type_` can come from either the HIR or from metadata. If it comes from HIR, it may be a type |
| /// alias instead of the final type. This will always have the final type, regardless of whether |
| /// `type_` came from HIR or from metadata. |
| /// |
| /// If `item_type.is_none()`, `type_` is guaranteed to come from metadata (and therefore hold the |
| /// final type). |
| pub(crate) item_type: Option<Type>, |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct BareFunctionDecl { |
| pub(crate) safety: hir::Safety, |
| pub(crate) generic_params: Vec<GenericParamDef>, |
| pub(crate) decl: FnDecl, |
| pub(crate) abi: ExternAbi, |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct UnsafeBinderTy { |
| pub(crate) generic_params: Vec<GenericParamDef>, |
| pub(crate) ty: Type, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Static { |
| pub(crate) type_: Box<Type>, |
| pub(crate) mutability: Mutability, |
| pub(crate) expr: Option<BodyId>, |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Hash, Debug)] |
| pub(crate) struct Constant { |
| pub(crate) generics: Generics, |
| pub(crate) kind: ConstantKind, |
| pub(crate) type_: Type, |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Hash, Debug)] |
| pub(crate) enum Term { |
| Type(Type), |
| Constant(ConstantKind), |
| } |
| |
| impl Term { |
| pub(crate) fn ty(&self) -> Option<&Type> { |
| if let Term::Type(ty) = self { Some(ty) } else { None } |
| } |
| } |
| |
| impl From<Type> for Term { |
| fn from(ty: Type) -> Self { |
| Term::Type(ty) |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Hash, Debug)] |
| pub(crate) enum ConstantKind { |
| /// This is the wrapper around `ty::Const` for a non-local constant. Because it doesn't have a |
| /// `BodyId`, we need to handle it on its own. |
| /// |
| /// Note that `ty::Const` includes generic parameters, and may not always be uniquely identified |
| /// by a DefId. So this field must be different from `Extern`. |
| TyConst { expr: Box<str> }, |
| /// A constant that is just a path (i.e., referring to a const param, free const, etc.). |
| // FIXME: this is an unfortunate representation. rustdoc's logic around consts needs to be improved. |
| Path { path: Box<str> }, |
| /// A constant (expression) that's not an item or associated item. These are usually found |
| /// nested inside types (e.g., array lengths) or expressions (e.g., repeat counts), and also |
| /// used to define explicit discriminant values for enum variants. |
| Anonymous { body: BodyId }, |
| /// A constant from a different crate. |
| Extern { def_id: DefId }, |
| /// `const FOO: u32 = ...;` |
| Local { def_id: DefId, body: BodyId }, |
| /// An inferred constant as in `[10u8; _]`. |
| Infer, |
| } |
| |
| impl ConstantKind { |
| pub(crate) fn expr(&self, tcx: TyCtxt<'_>) -> String { |
| match *self { |
| ConstantKind::TyConst { ref expr } => expr.to_string(), |
| ConstantKind::Path { ref path } => path.to_string(), |
| ConstantKind::Extern { def_id } => print_inlined_const(tcx, def_id), |
| ConstantKind::Local { body, .. } | ConstantKind::Anonymous { body } => { |
| rendered_const(tcx, tcx.hir_body(body), tcx.hir_body_owner_def_id(body)) |
| } |
| ConstantKind::Infer => "_".to_string(), |
| } |
| } |
| |
| pub(crate) fn value(&self, tcx: TyCtxt<'_>) -> Option<String> { |
| match *self { |
| ConstantKind::TyConst { .. } |
| | ConstantKind::Path { .. } |
| | ConstantKind::Anonymous { .. } |
| | ConstantKind::Infer => None, |
| ConstantKind::Extern { def_id } | ConstantKind::Local { def_id, .. } => { |
| print_evaluated_const(tcx, def_id, true, true) |
| } |
| } |
| } |
| |
| pub(crate) fn is_literal(&self, tcx: TyCtxt<'_>) -> bool { |
| match *self { |
| ConstantKind::TyConst { .. } |
| | ConstantKind::Extern { .. } |
| | ConstantKind::Path { .. } |
| | ConstantKind::Infer => false, |
| ConstantKind::Local { body, .. } | ConstantKind::Anonymous { body } => { |
| is_literal_expr(tcx, body.hir_id) |
| } |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Impl { |
| pub(crate) safety: hir::Safety, |
| pub(crate) generics: Generics, |
| pub(crate) trait_: Option<Path>, |
| pub(crate) for_: Type, |
| pub(crate) items: Vec<Item>, |
| pub(crate) polarity: ty::ImplPolarity, |
| pub(crate) kind: ImplKind, |
| } |
| |
| impl Impl { |
| pub(crate) fn provided_trait_methods(&self, tcx: TyCtxt<'_>) -> FxIndexSet<Symbol> { |
| self.trait_ |
| .as_ref() |
| .map(|t| t.def_id()) |
| .map(|did| tcx.provided_trait_methods(did).map(|meth| meth.name()).collect()) |
| .unwrap_or_default() |
| } |
| |
| pub(crate) fn is_negative_trait_impl(&self) -> bool { |
| matches!(self.polarity, ty::ImplPolarity::Negative) |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) enum ImplKind { |
| Normal, |
| Auto, |
| FakeVariadic, |
| Blanket(Box<Type>), |
| } |
| |
| impl ImplKind { |
| pub(crate) fn is_auto(&self) -> bool { |
| matches!(self, ImplKind::Auto) |
| } |
| |
| pub(crate) fn is_blanket(&self) -> bool { |
| matches!(self, ImplKind::Blanket(_)) |
| } |
| |
| pub(crate) fn is_fake_variadic(&self) -> bool { |
| matches!(self, ImplKind::FakeVariadic) |
| } |
| |
| pub(crate) fn as_blanket_ty(&self) -> Option<&Type> { |
| match self { |
| ImplKind::Blanket(ty) => Some(ty), |
| _ => None, |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Import { |
| pub(crate) kind: ImportKind, |
| /// The item being re-exported. |
| pub(crate) source: ImportSource, |
| pub(crate) should_be_displayed: bool, |
| } |
| |
| impl Import { |
| pub(crate) fn new_simple( |
| name: Symbol, |
| source: ImportSource, |
| should_be_displayed: bool, |
| ) -> Self { |
| Self { kind: ImportKind::Simple(name), source, should_be_displayed } |
| } |
| |
| pub(crate) fn new_glob(source: ImportSource, should_be_displayed: bool) -> Self { |
| Self { kind: ImportKind::Glob, source, should_be_displayed } |
| } |
| |
| pub(crate) fn imported_item_is_doc_hidden(&self, tcx: TyCtxt<'_>) -> bool { |
| self.source.did.is_some_and(|did| tcx.is_doc_hidden(did)) |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) enum ImportKind { |
| // use source as str; |
| Simple(Symbol), |
| // use source::*; |
| Glob, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct ImportSource { |
| pub(crate) path: Path, |
| pub(crate) did: Option<DefId>, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Macro { |
| pub(crate) source: String, |
| /// Whether the macro was defined via `macro_rules!` as opposed to `macro`. |
| pub(crate) macro_rules: bool, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct ProcMacro { |
| pub(crate) kind: MacroKind, |
| pub(crate) helpers: Vec<Symbol>, |
| } |
| |
| /// A constraint on an associated item. |
| /// |
| /// ### Examples |
| /// |
| /// * the `A = Ty` and `B = Ty` in `Trait<A = Ty, B = Ty>` |
| /// * the `G<Ty> = Ty` in `Trait<G<Ty> = Ty>` |
| /// * the `A: Bound` in `Trait<A: Bound>` |
| /// * the `RetTy` in `Trait(ArgTy, ArgTy) -> RetTy` |
| /// * the `C = { Ct }` in `Trait<C = { Ct }>` (feature `associated_const_equality`) |
| /// * the `f(..): Bound` in `Trait<f(..): Bound>` (feature `return_type_notation`) |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct AssocItemConstraint { |
| pub(crate) assoc: PathSegment, |
| pub(crate) kind: AssocItemConstraintKind, |
| } |
| |
| /// The kind of [associated item constraint][AssocItemConstraint]. |
| #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) enum AssocItemConstraintKind { |
| Equality { term: Term }, |
| Bound { bounds: Vec<GenericBound> }, |
| } |
| |
| // Some nodes are used a lot. Make sure they don't unintentionally get bigger. |
| #[cfg(target_pointer_width = "64")] |
| mod size_asserts { |
| use rustc_data_structures::static_assert_size; |
| |
| use super::*; |
| // tidy-alphabetical-start |
| static_assert_size!(Crate, 16); // frequently moved by-value |
| static_assert_size!(DocFragment, 48); |
| static_assert_size!(GenericArg, 32); |
| static_assert_size!(GenericArgs, 24); |
| static_assert_size!(GenericParamDef, 40); |
| static_assert_size!(Generics, 16); |
| static_assert_size!(Item, 8); |
| static_assert_size!(ItemInner, 144); |
| static_assert_size!(ItemKind, 48); |
| static_assert_size!(PathSegment, 32); |
| static_assert_size!(Type, 32); |
| // tidy-alphabetical-end |
| } |