diff options
Diffstat (limited to 'src/librustdoc')
31 files changed, 1368 insertions, 998 deletions
diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index f37a8d85361..f9c2465fb3c 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -12,7 +12,7 @@ path = "lib.rs" arrayvec = { version = "0.7", default-features = false } askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] } base64 = "0.21.7" -indexmap = "2" +indexmap = { version = "2", features = ["serde"] } itertools = "0.12" minifier = { version = "0.3.5", default-features = false } pulldown-cmark-escape = { version = "0.11.0", features = ["simd"] } @@ -21,7 +21,7 @@ rustdoc-json-types = { path = "../rustdoc-json-types" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" smallvec = "1.8.1" -stringdex = { version = "0.0.1-alpha9" } +stringdex = { version = "0.0.1-alpha10" } tempfile = "3" threadpool = "1.8.1" tracing = "0.1" diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index e204e1788ba..881a81b22f0 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -3,16 +3,20 @@ // FIXME: Once the portability lint RFC is implemented (see tracking issue #41619), // switch to use those structures instead. -use std::fmt::{self, Write}; -use std::{mem, ops}; +use std::sync::Arc; +use std::{fmt, mem, ops}; +use itertools::Either; use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::attrs::AttributeKind; +use rustc_middle::ty::TyCtxt; use rustc_session::parse::ParseSess; use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; +use {rustc_ast as ast, rustc_hir as hir}; -use crate::display::Joined as _; +use crate::display::{Joined as _, MaybeDisplay, Wrapped}; use crate::html::escape::Escape; #[cfg(test)] @@ -256,6 +260,36 @@ impl Cfg { fn omit_preposition(&self) -> bool { matches!(self, Cfg::True | Cfg::False) } + + pub(crate) fn strip_hidden(&self, hidden: &FxHashSet<Cfg>) -> Option<Self> { + match self { + Self::True | Self::False => Some(self.clone()), + Self::Cfg(..) => { + if !hidden.contains(self) { + Some(self.clone()) + } else { + None + } + } + Self::Not(cfg) => { + if let Some(cfg) = cfg.strip_hidden(hidden) { + Some(Self::Not(Box::new(cfg))) + } else { + None + } + } + Self::Any(cfgs) => { + let cfgs = + cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>(); + if cfgs.is_empty() { None } else { Some(Self::Any(cfgs)) } + } + Self::All(cfgs) => { + let cfgs = + cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>(); + if cfgs.is_empty() { None } else { Some(Self::All(cfgs)) } + } + } + } } impl ops::Not for Cfg { @@ -376,27 +410,20 @@ impl Format { Format::LongPlain => false, } } + + fn escape(self, s: &str) -> impl fmt::Display { + if self.is_html() { Either::Left(Escape(s)) } else { Either::Right(s) } + } } /// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used. struct Display<'a>(&'a Cfg, Format); -fn write_with_opt_paren<T: fmt::Display>( - fmt: &mut fmt::Formatter<'_>, - has_paren: bool, - obj: T, -) -> fmt::Result { - if has_paren { - fmt.write_char('(')?; - } - obj.fmt(fmt)?; - if has_paren { - fmt.write_char(')')?; +impl Display<'_> { + fn code_wrappers(&self) -> Wrapped<&'static str> { + if self.1.is_html() { Wrapped::with("<code>", "</code>") } else { Wrapped::with("`", "`") } } - Ok(()) -} -impl Display<'_> { fn display_sub_cfgs( &self, fmt: &mut fmt::Formatter<'_>, @@ -427,20 +454,17 @@ impl Display<'_> { sub_cfgs .iter() .map(|sub_cfg| { - fmt::from_fn(move |fmt| { - if let Cfg::Cfg(_, Some(feat)) = sub_cfg - && short_longhand - { - if self.1.is_html() { - write!(fmt, "<code>{feat}</code>")?; - } else { - write!(fmt, "`{feat}`")?; - } - } else { - write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?; - } - Ok(()) - }) + if let Cfg::Cfg(_, Some(feat)) = sub_cfg + && short_longhand + { + Either::Left(self.code_wrappers().wrap(feat)) + } else { + Either::Right( + Wrapped::with_parens() + .when(!sub_cfg.is_all()) + .wrap(Display(sub_cfg, self.1)), + ) + } }) .joined(separator, f) }) @@ -461,9 +485,9 @@ impl fmt::Display for Display<'_> { sub_cfgs .iter() .map(|sub_cfg| { - fmt::from_fn(|fmt| { - write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1)) - }) + Wrapped::with_parens() + .when(!sub_cfg.is_all()) + .wrap(Display(sub_cfg, self.1)) }) .joined(separator, fmt) } @@ -568,23 +592,276 @@ impl fmt::Display for Display<'_> { }; if !human_readable.is_empty() { fmt.write_str(human_readable) - } else if let Some(v) = value { - if self.1.is_html() { - write!( - fmt, - r#"<code>{}="{}"</code>"#, - Escape(name.as_str()), - Escape(v.as_str()) - ) + } else { + let value = value + .map(|v| fmt::from_fn(move |f| write!(f, "={}", self.1.escape(v.as_str())))) + .maybe_display(); + self.code_wrappers() + .wrap(format_args!("{}{value}", self.1.escape(name.as_str()))) + .fmt(fmt) + } + } + } + } +} + +/// This type keeps track of (doc) cfg information as we go down the item tree. +#[derive(Clone, Debug)] +pub(crate) struct CfgInfo { + /// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active + /// `doc(auto_cfg(show(...)))` cfgs. + hidden_cfg: FxHashSet<Cfg>, + /// Current computed `cfg`. Each time we enter a new item, this field is updated as well while + /// taking into account the `hidden_cfg` information. + current_cfg: Cfg, + /// Whether the `doc(auto_cfg())` feature is enabled or not at this point. + auto_cfg_active: bool, + /// If the parent item used `doc(cfg(...))`, then we don't want to overwrite `current_cfg`, + /// instead we will concatenate with it. However, if it's not the case, we need to overwrite + /// `current_cfg`. + parent_is_doc_cfg: bool, +} + +impl Default for CfgInfo { + fn default() -> Self { + Self { + hidden_cfg: FxHashSet::from_iter([ + Cfg::Cfg(sym::test, None), + Cfg::Cfg(sym::doc, None), + Cfg::Cfg(sym::doctest, None), + ]), + current_cfg: Cfg::True, + auto_cfg_active: true, + parent_is_doc_cfg: false, + } + } +} + +fn show_hide_show_conflict_error( + tcx: TyCtxt<'_>, + item_span: rustc_span::Span, + previous: rustc_span::Span, +) { + let mut diag = tcx.sess.dcx().struct_span_err( + item_span, + format!( + "same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item" + ), + ); + diag.span_note(previous, "first change was here"); + diag.emit(); +} + +/// This functions updates the `hidden_cfg` field of the provided `cfg_info` argument. +/// +/// It also checks if a same `cfg` is present in both `auto_cfg(hide(...))` and +/// `auto_cfg(show(...))` on the same item and emits an error if it's the case. +/// +/// Because we go through a list of `cfg`s, we keep track of the `cfg`s we saw in `new_show_attrs` +/// and in `new_hide_attrs` arguments. +fn handle_auto_cfg_hide_show( + tcx: TyCtxt<'_>, + cfg_info: &mut CfgInfo, + sub_attr: &MetaItemInner, + is_show: bool, + new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>, + new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>, +) { + if let MetaItemInner::MetaItem(item) = sub_attr + && let MetaItemKind::List(items) = &item.kind + { + for item in items { + // FIXME: Report in case `Cfg::parse` reports an error? + if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) { + if is_show { + if let Some(span) = new_hide_attrs.get(&(key, value)) { + show_hide_show_conflict_error(tcx, item.span(), *span); } else { - write!(fmt, r#"`{name}="{v}"`"#) + new_show_attrs.insert((key, value), item.span()); } - } else if self.1.is_html() { - write!(fmt, "<code>{}</code>", Escape(name.as_str())) + cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value)); } else { - write!(fmt, "`{name}`") + if let Some(span) = new_show_attrs.get(&(key, value)) { + show_hide_show_conflict_error(tcx, item.span(), *span); + } else { + new_hide_attrs.insert((key, value), item.span()); + } + cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value)); + } + } + } + } +} + +pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>( + attrs: I, + tcx: TyCtxt<'_>, + cfg_info: &mut CfgInfo, +) -> Option<Arc<Cfg>> { + fn single<T: IntoIterator>(it: T) -> Option<T::Item> { + let mut iter = it.into_iter(); + let item = iter.next()?; + if iter.next().is_some() { + return None; + } + Some(item) + } + + fn check_changed_auto_active_status( + changed_auto_active_status: &mut Option<rustc_span::Span>, + attr: &ast::MetaItem, + cfg_info: &mut CfgInfo, + tcx: TyCtxt<'_>, + new_value: bool, + ) -> bool { + if let Some(first_change) = changed_auto_active_status { + if cfg_info.auto_cfg_active != new_value { + tcx.sess + .dcx() + .struct_span_err( + vec![*first_change, attr.span], + "`auto_cfg` was disabled and enabled more than once on the same item", + ) + .emit(); + return true; + } + } else { + *changed_auto_active_status = Some(attr.span); + } + cfg_info.auto_cfg_active = new_value; + false + } + + let mut new_show_attrs = FxHashMap::default(); + let mut new_hide_attrs = FxHashMap::default(); + + let mut doc_cfg = attrs + .clone() + .filter(|attr| attr.has_name(sym::doc)) + .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) + .filter(|attr| attr.has_name(sym::cfg)) + .peekable(); + // If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes. + if doc_cfg.peek().is_some() { + let sess = tcx.sess; + // We overwrite existing `cfg`. + if !cfg_info.parent_is_doc_cfg { + cfg_info.current_cfg = Cfg::True; + cfg_info.parent_is_doc_cfg = true; + } + for attr in doc_cfg { + if let Some(cfg_mi) = + attr.meta_item().and_then(|attr| rustc_expand::config::parse_cfg(attr, sess)) + { + match Cfg::parse(cfg_mi) { + Ok(new_cfg) => cfg_info.current_cfg &= new_cfg, + Err(e) => { + sess.dcx().span_err(e.span, e.msg); + } + } + } + } + } else { + cfg_info.parent_is_doc_cfg = false; + } + + let mut changed_auto_active_status = None; + + // We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes. + for attr in attrs { + if let Some(ident) = attr.ident() + && ident.name == sym::doc + && let Some(attrs) = attr.meta_item_list() + { + for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) { + let MetaItemInner::MetaItem(attr) = attr else { + continue; + }; + match &attr.kind { + MetaItemKind::Word => { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + true, + ) { + return None; + } + } + MetaItemKind::NameValue(lit) => { + if let LitKind::Bool(value) = lit.kind { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + value, + ) { + return None; + } + } + } + MetaItemKind::List(sub_attrs) => { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + attr, + cfg_info, + tcx, + true, + ) { + return None; + } + for sub_attr in sub_attrs.iter() { + if let Some(ident) = sub_attr.ident() + && (ident.name == sym::show || ident.name == sym::hide) + { + handle_auto_cfg_hide_show( + tcx, + cfg_info, + &sub_attr, + ident.name == sym::show, + &mut new_show_attrs, + &mut new_hide_attrs, + ); + } + } + } } } + } else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr { + // Treat `#[target_feature(enable = "feat")]` attributes as if they were + // `#[doc(cfg(target_feature = "feat"))]` attributes as well. + for (feature, _) in features { + cfg_info.current_cfg &= Cfg::Cfg(sym::target_feature, Some(*feature)); + } + continue; + } else if !cfg_info.parent_is_doc_cfg + && let Some(ident) = attr.ident() + && matches!(ident.name, sym::cfg | sym::cfg_trace) + && let Some(attr) = single(attr.meta_item_list()?) + && let Ok(new_cfg) = Cfg::parse(&attr) + { + cfg_info.current_cfg &= new_cfg; + } + } + + // If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing + // to be done here. + if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg { + None + } else if cfg_info.parent_is_doc_cfg { + if cfg_info.current_cfg == Cfg::True { + None + } else { + Some(Arc::new(cfg_info.current_cfg.clone())) + } + } else { + // If `doc(auto_cfg)` feature is enabled, we want to collect all `cfg` items, we remove the + // hidden ones afterward. + match cfg_info.current_cfg.strip_hidden(&cfg_info.hidden_cfg) { + None | Some(Cfg::True) => None, + Some(cfg) => Some(Arc::new(cfg)), } } } diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 8461e15c6c3..8beea0580de 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -19,10 +19,10 @@ use tracing::{debug, trace}; use super::{Item, extract_cfg_from_attrs}; use crate::clean::{ - self, Attributes, ImplKind, ItemId, Type, clean_bound_vars, clean_generics, clean_impl_item, - clean_middle_assoc_item, clean_middle_field, clean_middle_ty, clean_poly_fn_sig, - clean_trait_ref_with_constraints, clean_ty, clean_ty_alias_inner_type, clean_ty_generics, - clean_variant_def, utils, + self, Attributes, CfgInfo, ImplKind, ItemId, Type, clean_bound_vars, clean_generics, + clean_impl_item, clean_middle_assoc_item, clean_middle_field, clean_middle_ty, + clean_poly_fn_sig, clean_trait_ref_with_constraints, clean_ty, clean_ty_alias_inner_type, + clean_ty_generics, clean_variant_def, utils, }; use crate::core::DocContext; use crate::formats::item_type::ItemType; @@ -409,6 +409,7 @@ pub(crate) fn merge_attrs( cx: &mut DocContext<'_>, old_attrs: &[hir::Attribute], new_attrs: Option<(&[hir::Attribute], Option<LocalDefId>)>, + cfg_info: &mut CfgInfo, ) -> (clean::Attributes, Option<Arc<clean::cfg::Cfg>>) { // NOTE: If we have additional attributes (from a re-export), // always insert them first. This ensure that re-export @@ -423,12 +424,12 @@ pub(crate) fn merge_attrs( } else { Attributes::from_hir(&both) }, - extract_cfg_from_attrs(both.iter(), cx.tcx, &cx.cache.hidden_cfg), + extract_cfg_from_attrs(both.iter(), cx.tcx, cfg_info), ) } else { ( Attributes::from_hir(old_attrs), - extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg), + extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, cfg_info), ) } } @@ -604,7 +605,11 @@ pub(crate) fn build_impl( }); } - let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs); + // In here, we pass an empty `CfgInfo` because the computation of `cfg` happens later, so it + // doesn't matter at this point. + // + // We need to pass this empty `CfgInfo` because `merge_attrs` is used when computing the `cfg`. + let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs, &mut CfgInfo::default()); trace!("merged_attrs={merged_attrs:?}"); trace!( diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 0afb969d5c8..4fd8d245089 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -58,6 +58,7 @@ use tracing::{debug, instrument}; use utils::*; use {rustc_ast as ast, rustc_hir as hir}; +pub(crate) use self::cfg::{CfgInfo, extract_cfg_from_attrs}; pub(crate) use self::types::*; pub(crate) use self::utils::{krate, register_res, synthesize_auto_trait_and_blanket_impls}; use crate::core::DocContext; @@ -212,18 +213,10 @@ fn generate_item_with_correct_attrs( // We only keep the item's attributes. target_attrs.iter().map(|attr| (Cow::Borrowed(attr), None)).collect() }; - let cfg = extract_cfg_from_attrs( - attrs.iter().map(move |(attr, _)| match attr { - Cow::Borrowed(attr) => *attr, - Cow::Owned(attr) => attr, - }), - cx.tcx, - &cx.cache.hidden_cfg, - ); let attrs = Attributes::from_hir_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false); let name = renamed.or(Some(name)); - let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, cfg); + let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, None); // FIXME (GuillaumeGomez): Should we also make `inline_stmt_id` a `Vec` instead of an `Option`? item.inner.inline_stmt_id = import_ids.first().copied(); item diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index bd3f4e9a6f2..f3662a67bbe 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -467,7 +467,7 @@ impl Item { name, kind, Attributes::from_hir(hir_attrs), - extract_cfg_from_attrs(hir_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg), + None, ) } @@ -794,50 +794,6 @@ impl Item { Some(tcx.visibility(def_id)) } - /// Get a list of attributes excluding `#[repr]` to display. - /// - /// Only used by the HTML output-format. - fn attributes_without_repr(&self) -> Vec<String> { - self.attrs - .other_attrs - .iter() - .filter_map(|attr| match attr { - hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => { - Some(format!("#[unsafe(link_section = \"{name}\")]")) - } - hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => { - Some("#[unsafe(no_mangle)]".to_string()) - } - hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => { - Some(format!("#[unsafe(export_name = \"{name}\")]")) - } - hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => { - Some("#[non_exhaustive]".to_string()) - } - _ => None, - }) - .collect() - } - - /// Get a list of attributes to display on this item. - /// - /// Only used by the HTML output-format. - pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> { - let mut attrs = self.attributes_without_repr(); - - if let Some(repr_attr) = self.repr(tcx, cache) { - attrs.push(repr_attr); - } - attrs - } - - /// Returns a stringified `#[repr(...)]` attribute. - /// - /// Only used by the HTML output-format. - pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> { - repr_attributes(tcx, cache, self.def_id()?, self.type_()) - } - pub fn is_doc_hidden(&self) -> bool { self.attrs.is_doc_hidden() } @@ -847,74 +803,6 @@ impl Item { } } -/// Return a string representing the `#[repr]` attribute if present. -/// -/// Only used by the HTML output-format. -pub(crate) fn repr_attributes( - tcx: TyCtxt<'_>, - cache: &Cache, - def_id: DefId, - item_type: ItemType, -) -> Option<String> { - use rustc_abi::IntegerType; - - if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) { - return None; - } - let adt = tcx.adt_def(def_id); - let repr = adt.repr(); - let mut out = Vec::new(); - if repr.c() { - out.push("C"); - } - if repr.transparent() { - // Render `repr(transparent)` iff the non-1-ZST field is public or at least one - // field is public in case all fields are 1-ZST fields. - let render_transparent = cache.document_private - || adt - .all_fields() - .find(|field| { - let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did)); - tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty)) - .is_ok_and(|layout| !layout.is_1zst()) - }) - .map_or_else( - || adt.all_fields().any(|field| field.vis.is_public()), - |field| field.vis.is_public(), - ); - - if render_transparent { - out.push("transparent"); - } - } - if repr.simd() { - out.push("simd"); - } - let pack_s; - if let Some(pack) = repr.pack { - pack_s = format!("packed({})", pack.bytes()); - out.push(&pack_s); - } - let align_s; - if let Some(align) = repr.align { - align_s = format!("align({})", align.bytes()); - out.push(&align_s); - } - let int_s; - if let Some(int) = repr.int { - int_s = match int { - IntegerType::Pointer(is_signed) => { - format!("{}size", if is_signed { 'i' } else { 'u' }) - } - IntegerType::Fixed(size, is_signed) => { - format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8) - } - }; - out.push(&int_s); - } - if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None } -} - #[derive(Clone, Debug)] pub(crate) enum ItemKind { ExternCrateItem { @@ -1014,30 +902,6 @@ impl ItemKind { | AttributeItem => [].iter(), } } - - /// Returns `true` if this item does not appear inside an impl block. - pub(crate) fn is_non_assoc(&self) -> bool { - matches!( - self, - StructItem(_) - | UnionItem(_) - | EnumItem(_) - | TraitItem(_) - | ModuleItem(_) - | ExternCrateItem { .. } - | FunctionItem(_) - | TypeAliasItem(_) - | StaticItem(_) - | ConstantItem(_) - | TraitAliasItem(_) - | ForeignFunctionItem(_, _) - | ForeignStaticItem(_, _) - | ForeignTypeItem - | MacroItem(_) - | ProcMacroItem(_) - | PrimitiveItem(_) - ) - } } #[derive(Clone, Debug)] @@ -1057,75 +921,6 @@ pub(crate) fn hir_attr_lists<'a, I: IntoIterator<Item = &'a hir::Attribute>>( .flatten() } -pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>( - attrs: I, - tcx: TyCtxt<'_>, - hidden_cfg: &FxHashSet<Cfg>, -) -> Option<Arc<Cfg>> { - let doc_cfg_active = tcx.features().doc_cfg(); - let doc_auto_cfg_active = tcx.features().doc_auto_cfg(); - - fn single<T: IntoIterator>(it: T) -> Option<T::Item> { - let mut iter = it.into_iter(); - let item = iter.next()?; - if iter.next().is_some() { - return None; - } - Some(item) - } - - let mut cfg = if doc_cfg_active || doc_auto_cfg_active { - let mut doc_cfg = attrs - .clone() - .filter(|attr| attr.has_name(sym::doc)) - .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) - .filter(|attr| attr.has_name(sym::cfg)) - .peekable(); - if doc_cfg.peek().is_some() && doc_cfg_active { - let sess = tcx.sess; - - doc_cfg.fold(Cfg::True, |mut cfg, item| { - if let Some(cfg_mi) = - item.meta_item().and_then(|item| rustc_expand::config::parse_cfg(item, sess)) - { - match Cfg::parse(cfg_mi) { - Ok(new_cfg) => cfg &= new_cfg, - Err(e) => { - sess.dcx().span_err(e.span, e.msg); - } - } - } - cfg - }) - } else if doc_auto_cfg_active { - // If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because - // `doc(cfg())` overrides `cfg()`). - attrs - .clone() - .filter(|attr| attr.has_name(sym::cfg_trace)) - .filter_map(|attr| single(attr.meta_item_list()?)) - .filter_map(|attr| Cfg::parse_without(attr.meta_item()?, hidden_cfg).ok().flatten()) - .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg) - } else { - Cfg::True - } - } else { - Cfg::True - }; - - // treat #[target_feature(enable = "feat")] attributes as if they were - // #[doc(cfg(target_feature = "feat"))] attributes as well - if let Some(features) = - find_attr!(attrs, AttributeKind::TargetFeature { features, .. } => features) - { - for (feature, _) in features { - cfg &= Cfg::Cfg(sym::target_feature, Some(*feature)); - } - } - - if cfg == Cfg::True { None } else { Some(Arc::new(cfg)) } -} - pub(crate) trait NestedAttributesExt { /// Returns `true` if the attribute list contains a specific `word` fn has_word(self, word: Symbol) -> bool @@ -1622,7 +1417,7 @@ impl Type { match (self_cleared, other_cleared) { // Recursive cases. (Type::Tuple(a), Type::Tuple(b)) => { - a.len() == b.len() && a.iter().zip(b).all(|(a, b)| a.is_doc_subtype_of(b, cache)) + 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), diff --git a/src/librustdoc/display.rs b/src/librustdoc/display.rs index db868c5c9a8..d62ea4c3688 100644 --- a/src/librustdoc/display.rs +++ b/src/librustdoc/display.rs @@ -1,6 +1,6 @@ //! Various utilities for working with [`fmt::Display`] implementations. -use std::fmt::{self, Display, Formatter}; +use std::fmt::{self, Display, Formatter, FormattingOptions}; pub(crate) trait Joined: IntoIterator { /// Takes an iterator over elements that implement [`Display`], and format them into `f`, separated by `sep`. @@ -45,3 +45,87 @@ impl<T: Display> MaybeDisplay for Option<T> { }) } } + +#[derive(Clone, Copy)] +pub(crate) struct Wrapped<T> { + prefix: T, + suffix: T, +} + +pub(crate) enum AngleBracket { + Open, + Close, +} + +impl Display for AngleBracket { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match (self, f.alternate()) { + (Self::Open, true) => "<", + (Self::Open, false) => "<", + (Self::Close, true) => ">", + (Self::Close, false) => ">", + }) + } +} + +impl Wrapped<AngleBracket> { + pub(crate) fn with_angle_brackets() -> Self { + Self { prefix: AngleBracket::Open, suffix: AngleBracket::Close } + } +} + +impl Wrapped<char> { + pub(crate) fn with_parens() -> Self { + Self { prefix: '(', suffix: ')' } + } + + pub(crate) fn with_square_brackets() -> Self { + Self { prefix: '[', suffix: ']' } + } +} + +impl<T: Display> Wrapped<T> { + pub(crate) fn with(prefix: T, suffix: T) -> Self { + Self { prefix, suffix } + } + + pub(crate) fn when(self, if_: bool) -> Wrapped<impl Display> { + Wrapped { + prefix: if_.then_some(self.prefix).maybe_display(), + suffix: if_.then_some(self.suffix).maybe_display(), + } + } + + pub(crate) fn wrap_fn( + self, + content: impl Fn(&mut Formatter<'_>) -> fmt::Result, + ) -> impl Display { + fmt::from_fn(move |f| { + self.prefix.fmt(f)?; + content(f)?; + self.suffix.fmt(f) + }) + } + + pub(crate) fn wrap<C: Display>(self, content: C) -> impl Display { + self.wrap_fn(move |f| content.fmt(f)) + } +} + +#[derive(Clone, Copy)] +pub(crate) struct WithOpts { + opts: FormattingOptions, +} + +impl WithOpts { + pub(crate) fn from(f: &Formatter<'_>) -> Self { + Self { opts: f.options() } + } + + pub(crate) fn display(self, t: impl Display) -> impl Display { + fmt::from_fn(move |f| { + let mut f = f.with_options(self.opts); + t.fmt(&mut f) + }) + } +} diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 95bd31729de..9499258f983 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -404,11 +404,15 @@ pub(crate) fn run_tests( std::mem::drop(temp_dir.take()); times.display_times(); }); + } else { + // If the first condition branch exited successfully, `test_main_with_exit_callback` will + // not exit the process. So to prevent displaying the times twice, we put it behind an + // `else` condition. + times.display_times(); } + // We ensure temp dir destructor is called. + std::mem::drop(temp_dir); if nb_errors != 0 { - // We ensure temp dir destructor is called. - std::mem::drop(temp_dir); - times.display_times(); std::process::exit(test::ERROR_EXIT_CODE); } } diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index f5ec828187a..4d3f976c2a6 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -5,7 +5,6 @@ use std::env; use std::sync::Arc; use rustc_ast_pretty::pprust; -use rustc_data_structures::fx::FxHashSet; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit}; use rustc_middle::hir::nested_filter; @@ -15,7 +14,7 @@ use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span, sym}; use super::{DocTestVisitor, ScrapedDocTest}; -use crate::clean::{Attributes, extract_cfg_from_attrs}; +use crate::clean::{Attributes, CfgInfo, extract_cfg_from_attrs}; use crate::html::markdown::{self, ErrorCodes, LangString, MdRelLine}; struct RustCollector { @@ -121,7 +120,7 @@ impl HirCollector<'_> { ) { let ast_attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id)); if let Some(ref cfg) = - extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &FxHashSet::default()) + extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &mut CfgInfo::default()) && !cfg.matches(&self.tcx.sess.psess) { return; diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 75c848742ea..5e5592269af 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -125,8 +125,6 @@ pub(crate) struct Cache { /// /// Links are indexed by the DefId of the item they document. pub(crate) intra_doc_links: FxHashMap<ItemId, FxIndexSet<clean::ItemLink>>, - /// Cfg that have been hidden via #![doc(cfg_hide(...))] - pub(crate) hidden_cfg: FxHashSet<clean::cfg::Cfg>, /// Contains the list of `DefId`s which have been inlined. It is used when generating files /// to check if a stripped item should get its file generated or not: if it's inside a diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 8c75f301841..856e637a458 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -30,17 +30,13 @@ use super::url_parts_builder::UrlPartsBuilder; use crate::clean::types::ExternalLocation; use crate::clean::utils::find_nearest_parent_module; use crate::clean::{self, ExternalCrate, PrimitiveType}; -use crate::display::{Joined as _, MaybeDisplay as _}; +use crate::display::{Joined as _, MaybeDisplay as _, WithOpts, Wrapped}; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::escape::{Escape, EscapeBodyText}; use crate::html::render::Context; use crate::passes::collect_intra_doc_links::UrlFragment; -pub(crate) fn write_str(s: &mut String, f: fmt::Arguments<'_>) { - s.write_fmt(f).unwrap(); -} - pub(crate) fn print_generic_bounds( bounds: &[clean::GenericBound], cx: &Context<'_>, @@ -105,20 +101,16 @@ impl clean::GenericParamDef { impl clean::Generics { pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - let mut real_params = self.params.iter().filter(|p| !p.is_synthetic_param()).peekable(); - if real_params.peek().is_none() { - return Ok(()); - } - - let real_params = - fmt::from_fn(|f| real_params.clone().map(|g| g.print(cx)).joined(", ", f)); - if f.alternate() { - write!(f, "<{real_params:#}>") - } else { - write!(f, "<{real_params}>") - } - }) + let mut real_params = self.params.iter().filter(|p| !p.is_synthetic_param()).peekable(); + if real_params.peek().is_none() { + None + } else { + Some( + Wrapped::with_angle_brackets() + .wrap_fn(move |f| real_params.clone().map(|g| g.print(cx)).joined(", ", f)), + ) + } + .maybe_display() } } @@ -151,11 +143,8 @@ fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> Ok(()) } clean::WherePredicate::EqPredicate { lhs, rhs } => { - if f.alternate() { - write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx)) - } else { - write!(f, "{} == {}", lhs.print(cx), rhs.print(cx)) - } + let opts = WithOpts::from(f); + write!(f, "{} == {}", opts.display(lhs.print(cx)), opts.display(rhs.print(cx))) } } }) @@ -279,13 +268,10 @@ impl clean::GenericBound { ty.print(cx).fmt(f) } clean::GenericBound::Use(args) => { - if f.alternate() { - f.write_str("use<")?; - } else { - f.write_str("use<")?; - } - args.iter().map(|arg| arg.name()).joined(", ", f)?; - if f.alternate() { f.write_str(">") } else { f.write_str(">") } + f.write_str("use")?; + Wrapped::with_angle_brackets() + .wrap_fn(|f| args.iter().map(|arg| arg.name()).joined(", ", f)) + .fmt(f) } }) } @@ -297,40 +283,29 @@ impl clean::GenericArgs { match self { clean::GenericArgs::AngleBracketed { args, constraints } => { if !args.is_empty() || !constraints.is_empty() { - if f.alternate() { - f.write_str("<")?; - } else { - f.write_str("<")?; - } - - [Either::Left(args), Either::Right(constraints)] - .into_iter() - .flat_map(Either::factor_into_iter) - .map(|either| { - either.map_either( - |arg| arg.print(cx), - |constraint| constraint.print(cx), - ) + Wrapped::with_angle_brackets() + .wrap_fn(|f| { + [Either::Left(args), Either::Right(constraints)] + .into_iter() + .flat_map(Either::factor_into_iter) + .map(|either| { + either.map_either( + |arg| arg.print(cx), + |constraint| constraint.print(cx), + ) + }) + .joined(", ", f) }) - .joined(", ", f)?; - - if f.alternate() { - f.write_str(">")?; - } else { - f.write_str(">")?; - } + .fmt(f)?; } } clean::GenericArgs::Parenthesized { inputs, output } => { - f.write_str("(")?; - inputs.iter().map(|ty| ty.print(cx)).joined(", ", f)?; - f.write_str(")")?; + Wrapped::with_parens() + .wrap_fn(|f| inputs.iter().map(|ty| ty.print(cx)).joined(", ", f)) + .fmt(f)?; if let Some(ref ty) = *output { - if f.alternate() { - write!(f, " -> {:#}", ty.print(cx))?; - } else { - write!(f, " -> {}", ty.print(cx))?; - } + f.write_str(if f.alternate() { " -> " } else { " -> " })?; + ty.print(cx).fmt(f)?; } } clean::GenericArgs::ReturnTypeNotation => { @@ -834,9 +809,10 @@ fn print_higher_ranked_params_with_space( fmt::from_fn(move |f| { if !params.is_empty() { f.write_str(keyword)?; - f.write_str(if f.alternate() { "<" } else { "<" })?; - params.iter().map(|lt| lt.print(cx)).joined(", ", f)?; - f.write_str(if f.alternate() { "> " } else { "> " })?; + Wrapped::with_angle_brackets() + .wrap_fn(|f| params.iter().map(|lt| lt.print(cx)).joined(", ", f)) + .fmt(f)?; + f.write_char(' ')?; } Ok(()) }) @@ -923,26 +899,23 @@ fn fmt_type( f, PrimitiveType::Tuple, format_args!( - "({})", - fmt::from_fn(|f| generic_names.iter().joined(", ", f)) + "{}", + Wrapped::with_parens() + .wrap_fn(|f| generic_names.iter().joined(", ", f)) ), cx, ) } else { - f.write_str("(")?; - many.iter().map(|item| item.print(cx)).joined(", ", f)?; - f.write_str(")") + Wrapped::with_parens() + .wrap_fn(|f| many.iter().map(|item| item.print(cx)).joined(", ", f)) + .fmt(f) } } }, clean::Slice(box clean::Generic(name)) => { primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx) } - clean::Slice(t) => { - write!(f, "[")?; - t.print(cx).fmt(f)?; - write!(f, "]") - } + clean::Slice(t) => Wrapped::with_square_brackets().wrap(t.print(cx)).fmt(f), clean::Type::Pat(t, pat) => { fmt::Display::fmt(&t.print(cx), f)?; write!(f, " is {pat}") @@ -953,40 +926,27 @@ fn fmt_type( format_args!("[{name}; {n}]", n = Escape(n)), cx, ), - clean::Array(t, n) => { - write!(f, "[")?; - t.print(cx).fmt(f)?; - if f.alternate() { - write!(f, "; {n}")?; - } else { - write!(f, "; ")?; - primitive_link(f, PrimitiveType::Array, format_args!("{n}", n = Escape(n)), cx)?; - } - write!(f, "]") - } - clean::RawPointer(m, t) => { - let m = match m { - hir::Mutability::Mut => "mut", - hir::Mutability::Not => "const", - }; - - if matches!(**t, clean::Generic(_)) || t.is_assoc_ty() { - let ty = t.print(cx); + clean::Array(t, n) => Wrapped::with_square_brackets() + .wrap(fmt::from_fn(|f| { + t.print(cx).fmt(f)?; + f.write_str("; ")?; if f.alternate() { - primitive_link( - f, - clean::PrimitiveType::RawPointer, - format_args!("*{m} {ty:#}"), - cx, - ) + f.write_str(n) } else { - primitive_link( - f, - clean::PrimitiveType::RawPointer, - format_args!("*{m} {ty}"), - cx, - ) + primitive_link(f, PrimitiveType::Array, format_args!("{n}", n = Escape(n)), cx) } + })) + .fmt(f), + clean::RawPointer(m, t) => { + let m = m.ptr_str(); + + if matches!(**t, clean::Generic(_)) || t.is_assoc_ty() { + primitive_link( + f, + clean::PrimitiveType::RawPointer, + format_args!("*{m} {ty}", ty = WithOpts::from(f).display(t.print(cx))), + cx, + ) } else { primitive_link(f, clean::PrimitiveType::RawPointer, format_args!("*{m} "), cx)?; t.print(cx).fmt(f) @@ -1020,14 +980,10 @@ fn fmt_type( clean::ImplTrait(ref bounds) if bounds.len() > 1 => true, _ => false, }; - if needs_parens { - f.write_str("(")?; - } - fmt_type(ty, f, use_absolute, cx)?; - if needs_parens { - f.write_str(")")?; - } - Ok(()) + Wrapped::with_parens() + .when(needs_parens) + .wrap_fn(|f| fmt_type(ty, f, use_absolute, cx)) + .fmt(f) } clean::ImplTrait(bounds) => { f.write_str("impl ")?; @@ -1057,23 +1013,21 @@ impl clean::QPathData { // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719), // we need to surround them with angle brackets in some cases (e.g. `<dyn …>::P`). - if f.alternate() { - if let Some(trait_) = trait_ - && should_fully_qualify - { - write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))? - } else { - write!(f, "{:#}::", self_type.print(cx))? - } + if let Some(trait_) = trait_ + && should_fully_qualify + { + let opts = WithOpts::from(f); + Wrapped::with_angle_brackets() + .wrap(format_args!( + "{} as {}", + opts.display(self_type.print(cx)), + opts.display(trait_.print(cx)) + )) + .fmt(f)? } else { - if let Some(trait_) = trait_ - && should_fully_qualify - { - write!(f, "<{} as {}>::", self_type.print(cx), trait_.print(cx))? - } else { - write!(f, "{}::", self_type.print(cx))? - } - }; + self_type.print(cx).fmt(f)?; + } + f.write_str("::")?; // It's pretty unsightly to look at `<A as B>::C` in output, and // we've got hyperlinking on our side, so try to avoid longer // notation as much as possible by making `C` a hyperlink to trait @@ -1132,7 +1086,7 @@ impl clean::Impl { if let Some(ref ty) = self.trait_ { if self.is_negative_trait_impl() { - write!(f, "!")?; + f.write_char('!')?; } if self.kind.is_fake_variadic() && let Some(generics) = ty.generics() @@ -1140,18 +1094,17 @@ impl clean::Impl { { let last = ty.last(); if f.alternate() { - write!(f, "{last}<")?; - self.print_type(inner_type, f, use_absolute, cx)?; - write!(f, ">")?; + write!(f, "{last}")?; } else { - write!(f, "{}<", print_anchor(ty.def_id(), last, cx))?; - self.print_type(inner_type, f, use_absolute, cx)?; - write!(f, ">")?; - } + write!(f, "{}", print_anchor(ty.def_id(), last, cx))?; + }; + Wrapped::with_angle_brackets() + .wrap_fn(|f| self.print_type(inner_type, f, use_absolute, cx)) + .fmt(f)?; } else { ty.print(cx).fmt(f)?; } - write!(f, " for ")?; + f.write_str(" for ")?; } if let Some(ty) = self.kind.as_blanket_ty() { @@ -1218,18 +1171,10 @@ impl clean::Impl { && let Ok(ty) = generics.exactly_one() && self.kind.is_fake_variadic() { - let wrapper = print_anchor(path.def_id(), path.last(), cx); - if f.alternate() { - write!(f, "{wrapper:#}<")?; - } else { - write!(f, "{wrapper}<")?; - } - self.print_type(ty, f, use_absolute, cx)?; - if f.alternate() { - write!(f, ">")?; - } else { - write!(f, ">")?; - } + print_anchor(path.def_id(), path.last(), cx).fmt(f)?; + Wrapped::with_angle_brackets() + .wrap_fn(|f| self.print_type(ty, f, use_absolute, cx)) + .fmt(f)?; } else { fmt_type(type_, f, use_absolute, cx)?; } @@ -1311,23 +1256,13 @@ impl clean::FnDecl { pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { let ellipsis = if self.c_variadic { ", ..." } else { "" }; - if f.alternate() { - write!( - f, - "({params:#}{ellipsis}){arrow:#}", - params = print_params(&self.inputs, cx), - ellipsis = ellipsis, - arrow = self.print_output(cx) - ) - } else { - write!( - f, - "({params}{ellipsis}){arrow}", - params = print_params(&self.inputs, cx), - ellipsis = ellipsis, - arrow = self.print_output(cx) - ) - } + Wrapped::with_parens() + .wrap_fn(|f| { + print_params(&self.inputs, cx).fmt(f)?; + f.write_str(ellipsis) + }) + .fmt(f)?; + self.print_output(cx).fmt(f) }) } @@ -1346,8 +1281,7 @@ impl clean::FnDecl { fmt::from_fn(move |f| { // First, generate the text form of the declaration, with no line wrapping, and count the bytes. let mut counter = WriteCounter(0); - write!(&mut counter, "{:#}", fmt::from_fn(|f| { self.inner_full_print(None, f, cx) })) - .unwrap(); + write!(&mut counter, "{:#}", fmt::from_fn(|f| { self.inner_full_print(None, f, cx) }))?; // If the text form was over 80 characters wide, we will line-wrap our output. let line_wrapping_indent = if header_len + counter.0 > 80 { Some(indent) } else { None }; @@ -1365,53 +1299,56 @@ impl clean::FnDecl { f: &mut fmt::Formatter<'_>, cx: &Context<'_>, ) -> fmt::Result { - f.write_char('(')?; - - if !self.inputs.is_empty() { - let line_wrapping_indent = line_wrapping_indent.map(|n| Indent(n + 4)); + Wrapped::with_parens() + .wrap_fn(|f| { + if !self.inputs.is_empty() { + let line_wrapping_indent = line_wrapping_indent.map(|n| Indent(n + 4)); - if let Some(indent) = line_wrapping_indent { - write!(f, "\n{indent}")?; - } + if let Some(indent) = line_wrapping_indent { + write!(f, "\n{indent}")?; + } - let sep = fmt::from_fn(|f| { - if let Some(indent) = line_wrapping_indent { - write!(f, ",\n{indent}") - } else { - f.write_str(", ") - } - }); + let sep = fmt::from_fn(|f| { + if let Some(indent) = line_wrapping_indent { + write!(f, ",\n{indent}") + } else { + f.write_str(", ") + } + }); - self.inputs.iter().map(|param| param.print(cx)).joined(sep, f)?; + self.inputs.iter().map(|param| param.print(cx)).joined(sep, f)?; - if line_wrapping_indent.is_some() { - writeln!(f, ",")? - } + if line_wrapping_indent.is_some() { + writeln!(f, ",")? + } - if self.c_variadic { - match line_wrapping_indent { - None => write!(f, ", ...")?, - Some(indent) => writeln!(f, "{indent}...")?, - }; - } - } + if self.c_variadic { + match line_wrapping_indent { + None => write!(f, ", ...")?, + Some(indent) => writeln!(f, "{indent}...")?, + }; + } + } - if let Some(n) = line_wrapping_indent { - write!(f, "{}", Indent(n))? - } + if let Some(n) = line_wrapping_indent { + write!(f, "{}", Indent(n))? + } - f.write_char(')')?; + Ok(()) + }) + .fmt(f)?; self.print_output(cx).fmt(f) } fn print_output(&self, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| match &self.output { - clean::Tuple(tys) if tys.is_empty() => Ok(()), - ty if f.alternate() => { - write!(f, " -> {:#}", ty.print(cx)) + fmt::from_fn(move |f| { + if self.output.is_unit() { + return Ok(()); } - ty => write!(f, " -> {}", ty.print(cx)), + + f.write_str(if f.alternate() { " -> " } else { " -> " })?; + self.output.print(cx).fmt(f) }) } } @@ -1422,10 +1359,13 @@ pub(crate) fn visibility_print_with_space(item: &clean::Item, cx: &Context<'_>) f.write_str("#[doc(hidden)] ")?; } - match item.visibility(cx.tcx()) { - None => {} - Some(ty::Visibility::Public) => f.write_str("pub ")?, - Some(ty::Visibility::Restricted(vis_did)) => { + let Some(vis) = item.visibility(cx.tcx()) else { + return Ok(()); + }; + + match vis { + ty::Visibility::Public => f.write_str("pub ")?, + ty::Visibility::Restricted(vis_did) => { // FIXME(camelid): This may not work correctly if `item_did` is a module. // However, rustdoc currently never displays a module's // visibility, so it shouldn't matter. diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 0e06361024b..1dcb4dcc3ff 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -8,17 +8,20 @@ use std::borrow::Cow; use std::collections::VecDeque; use std::fmt::{self, Display, Write}; +use std::iter; use rustc_data_structures::fx::FxIndexMap; use rustc_lexer::{Cursor, FrontmatterAllowed, LiteralKind, TokenKind}; +use rustc_span::BytePos; use rustc_span::edition::Edition; use rustc_span::symbol::Symbol; -use rustc_span::{BytePos, DUMMY_SP, Span}; -use super::format::{self, write_str}; +use super::format; use crate::clean::PrimitiveType; +use crate::display::Joined as _; use crate::html::escape::EscapeBodyText; use crate::html::macro_expansion::ExpandedCode; +use crate::html::render::span_map::{DUMMY_SP, Span}; use crate::html::render::{Context, LinkFromSrc}; /// This type is needed in case we want to render links on items to allow to go to their definition. @@ -45,92 +48,72 @@ pub(crate) enum Tooltip { CompileFail, ShouldPanic, Edition(Edition), - None, } /// Highlights `src` as an inline example, returning the HTML output. pub(crate) fn render_example_with_highlighting( src: &str, - out: &mut String, - tooltip: Tooltip, + tooltip: Option<&Tooltip>, playground_button: Option<&str>, extra_classes: &[String], -) { - write_header(out, "rust-example-rendered", None, tooltip, extra_classes); - write_code(out, src, None, None, None); - write_footer(out, playground_button); +) -> impl Display { + fmt::from_fn(move |f| { + write_header("rust-example-rendered", tooltip, extra_classes).fmt(f)?; + write_code(f, src, None, None, None); + write_footer(playground_button).fmt(f) + }) } -fn write_header( - out: &mut String, - class: &str, - extra_content: Option<&str>, - tooltip: Tooltip, - extra_classes: &[String], -) { - write_str( - out, - format_args!( +fn write_header(class: &str, tooltip: Option<&Tooltip>, extra_classes: &[String]) -> impl Display { + fmt::from_fn(move |f| { + write!( + f, "<div class=\"example-wrap{}\">", - match tooltip { - Tooltip::IgnoreAll | Tooltip::IgnoreSome(_) => " ignore", - Tooltip::CompileFail => " compile_fail", - Tooltip::ShouldPanic => " should_panic", - Tooltip::Edition(_) => " edition", - Tooltip::None => "", - } - ), - ); - - if tooltip != Tooltip::None { - let tooltip = fmt::from_fn(|f| match &tooltip { - Tooltip::IgnoreAll => f.write_str("This example is not tested"), - Tooltip::IgnoreSome(platforms) => { - f.write_str("This example is not tested on ")?; - match &platforms[..] { - [] => unreachable!(), - [platform] => f.write_str(platform)?, - [first, second] => write!(f, "{first} or {second}")?, - [platforms @ .., last] => { - for platform in platforms { - write!(f, "{platform}, ")?; + tooltip + .map(|tooltip| match tooltip { + Tooltip::IgnoreAll | Tooltip::IgnoreSome(_) => " ignore", + Tooltip::CompileFail => " compile_fail", + Tooltip::ShouldPanic => " should_panic", + Tooltip::Edition(_) => " edition", + }) + .unwrap_or_default() + )?; + + if let Some(tooltip) = tooltip { + let tooltip = fmt::from_fn(|f| match tooltip { + Tooltip::IgnoreAll => f.write_str("This example is not tested"), + Tooltip::IgnoreSome(platforms) => { + f.write_str("This example is not tested on ")?; + match &platforms[..] { + [] => unreachable!(), + [platform] => f.write_str(platform)?, + [first, second] => write!(f, "{first} or {second}")?, + [platforms @ .., last] => { + for platform in platforms { + write!(f, "{platform}, ")?; + } + write!(f, "or {last}")?; } - write!(f, "or {last}")?; } + Ok(()) } - Ok(()) - } - Tooltip::CompileFail => f.write_str("This example deliberately fails to compile"), - Tooltip::ShouldPanic => f.write_str("This example panics"), - Tooltip::Edition(edition) => write!(f, "This example runs with edition {edition}"), - Tooltip::None => unreachable!(), + Tooltip::CompileFail => f.write_str("This example deliberately fails to compile"), + Tooltip::ShouldPanic => f.write_str("This example panics"), + Tooltip::Edition(edition) => write!(f, "This example runs with edition {edition}"), + }); + + write!(f, "<a href=\"#\" class=\"tooltip\" title=\"{tooltip}\">ⓘ</a>")?; + } + + let classes = fmt::from_fn(|f| { + iter::once("rust") + .chain(Some(class).filter(|class| !class.is_empty())) + .chain(extra_classes.iter().map(String::as_str)) + .joined(" ", f) }); - write_str(out, format_args!("<a href=\"#\" class=\"tooltip\" title=\"{tooltip}\">ⓘ</a>")); - } - if let Some(extra) = extra_content { - out.push_str(extra); - } - if class.is_empty() { - write_str( - out, - format_args!( - "<pre class=\"rust{}{}\">", - if extra_classes.is_empty() { "" } else { " " }, - extra_classes.join(" ") - ), - ); - } else { - write_str( - out, - format_args!( - "<pre class=\"rust {class}{}{}\">", - if extra_classes.is_empty() { "" } else { " " }, - extra_classes.join(" ") - ), - ); - } - write_str(out, format_args!("<code>")); + write!(f, "<pre class=\"{classes}\"><code>") + }) } /// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None` @@ -577,8 +560,8 @@ pub(super) fn write_code( }); } -fn write_footer(out: &mut String, playground_button: Option<&str>) { - write_str(out, format_args!("</code></pre>{}</div>", playground_button.unwrap_or_default())); +fn write_footer(playground_button: Option<&str>) -> impl Display { + fmt::from_fn(move |f| write!(f, "</code></pre>{}</div>", playground_button.unwrap_or_default())) } /// How a span of text is classified. Mostly corresponds to token kinds. @@ -1262,6 +1245,64 @@ fn string<W: Write>( } } +fn generate_link_to_def( + out: &mut impl Write, + text_s: &str, + klass: Class, + href_context: &Option<HrefContext<'_, '_>>, + def_span: Span, + open_tag: bool, +) -> bool { + if let Some(href_context) = href_context + && let Some(href) = + href_context.context.shared.span_correspondence_map.get(&def_span).and_then(|href| { + let context = href_context.context; + // FIXME: later on, it'd be nice to provide two links (if possible) for all items: + // one to the documentation page and one to the source definition. + // FIXME: currently, external items only generate a link to their documentation, + // a link to their definition can be generated using this: + // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338 + match href { + LinkFromSrc::Local(span) => { + context.href_from_span_relative(*span, &href_context.current_href) + } + LinkFromSrc::External(def_id) => { + format::href_with_root_path(*def_id, context, Some(href_context.root_path)) + .ok() + .map(|(url, _, _)| url) + } + LinkFromSrc::Primitive(prim) => format::href_with_root_path( + PrimitiveType::primitive_locations(context.tcx())[prim], + context, + Some(href_context.root_path), + ) + .ok() + .map(|(url, _, _)| url), + LinkFromSrc::Doc(def_id) => { + format::href_with_root_path(*def_id, context, Some(href_context.root_path)) + .ok() + .map(|(doc_link, _, _)| doc_link) + } + } + }) + { + if !open_tag { + // We're already inside an element which has the same klass, no need to give it + // again. + write!(out, "<a href=\"{href}\">{text_s}").unwrap(); + } else { + let klass_s = klass.as_html(); + if klass_s.is_empty() { + write!(out, "<a href=\"{href}\">{text_s}").unwrap(); + } else { + write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap(); + } + } + return true; + } + false +} + /// This function writes `text` into `out` with some modifications depending on `klass`: /// /// * If `klass` is `None`, `text` is written into `out` with no modification. @@ -1291,10 +1332,14 @@ fn string_without_closing_tag<T: Display>( return Some("</span>"); }; + let mut added_links = false; let mut text_s = text.to_string(); if text_s.contains("::") { + let mut span = def_span.with_hi(def_span.lo()); text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| { + span = span.with_hi(span.hi() + BytePos(t.len() as _)); match t { + "::" => write!(&mut path, "::"), "self" | "Self" => write!( &mut path, "<span class=\"{klass}\">{t}</span>", @@ -1307,58 +1352,24 @@ fn string_without_closing_tag<T: Display>( klass = Class::KeyWord.as_html(), ) } - t => write!(&mut path, "{t}"), + t => { + if !t.is_empty() + && generate_link_to_def(&mut path, t, klass, href_context, span, open_tag) + { + added_links = true; + write!(&mut path, "</a>") + } else { + write!(&mut path, "{t}") + } + } } .expect("Failed to build source HTML path"); + span = span.with_lo(span.lo() + BytePos(t.len() as _)); path }); } - if let Some(href_context) = href_context - && let Some(href) = href_context.context.shared.span_correspondence_map.get(&def_span) - && let Some(href) = { - let context = href_context.context; - // FIXME: later on, it'd be nice to provide two links (if possible) for all items: - // one to the documentation page and one to the source definition. - // FIXME: currently, external items only generate a link to their documentation, - // a link to their definition can be generated using this: - // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338 - match href { - LinkFromSrc::Local(span) => { - context.href_from_span_relative(*span, &href_context.current_href) - } - LinkFromSrc::External(def_id) => { - format::href_with_root_path(*def_id, context, Some(href_context.root_path)) - .ok() - .map(|(url, _, _)| url) - } - LinkFromSrc::Primitive(prim) => format::href_with_root_path( - PrimitiveType::primitive_locations(context.tcx())[prim], - context, - Some(href_context.root_path), - ) - .ok() - .map(|(url, _, _)| url), - LinkFromSrc::Doc(def_id) => { - format::href_with_root_path(*def_id, context, Some(href_context.root_path)) - .ok() - .map(|(doc_link, _, _)| doc_link) - } - } - } - { - if !open_tag { - // We're already inside an element which has the same klass, no need to give it - // again. - write!(out, "<a href=\"{href}\">{text_s}").unwrap(); - } else { - let klass_s = klass.as_html(); - if klass_s.is_empty() { - write!(out, "<a href=\"{href}\">{text_s}").unwrap(); - } else { - write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap(); - } - } + if !added_links && generate_link_to_def(out, &text_s, klass, href_context, def_span, open_tag) { return Some("</a>"); } if !open_tag { diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index 2603e887bea..4d1bee9b3a1 100644 --- a/src/librustdoc/html/highlight/tests.rs +++ b/src/librustdoc/html/highlight/tests.rs @@ -1,6 +1,7 @@ use expect_test::expect_file; use rustc_data_structures::fx::FxIndexMap; use rustc_span::create_default_session_globals_then; +use test::Bencher; use super::{DecorationInfo, write_code}; @@ -81,3 +82,16 @@ let a = 4;"; expect_file!["fixtures/decorations.html"].assert_eq(&html); }); } + +#[bench] +fn bench_html_highlighting(b: &mut Bencher) { + let src = include_str!("../../../../compiler/rustc_ast/src/visit.rs"); + + create_default_session_globals_then(|| { + b.iter(|| { + let mut out = String::new(); + write_code(&mut out, src, None, None, None); + out + }); + }); +} diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 4addf2c3c96..7065de14c8e 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -321,31 +321,34 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> { )) }); - let tooltip = if ignore == Ignore::All { - highlight::Tooltip::IgnoreAll - } else if let Ignore::Some(platforms) = ignore { - highlight::Tooltip::IgnoreSome(platforms) - } else if compile_fail { - highlight::Tooltip::CompileFail - } else if should_panic { - highlight::Tooltip::ShouldPanic - } else if explicit_edition { - highlight::Tooltip::Edition(edition) - } else { - highlight::Tooltip::None + let tooltip = { + use highlight::Tooltip::*; + + if ignore == Ignore::All { + Some(IgnoreAll) + } else if let Ignore::Some(platforms) = ignore { + Some(IgnoreSome(platforms)) + } else if compile_fail { + Some(CompileFail) + } else if should_panic { + Some(ShouldPanic) + } else if explicit_edition { + Some(Edition(edition)) + } else { + None + } }; // insert newline to clearly separate it from the // previous block so we can shorten the html output - let mut s = String::new(); - s.push('\n'); - - highlight::render_example_with_highlighting( - &text, - &mut s, - tooltip, - playground_button.as_deref(), - &added_classes, + let s = format!( + "\n{}", + highlight::render_example_with_highlighting( + &text, + tooltip.as_ref(), + playground_button.as_deref(), + &added_classes, + ) ); Some(Event::Html(s.into())) } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 5f92ab2fada..4c06d0da470 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -30,6 +30,7 @@ use crate::formats::item_type::ItemType; use crate::html::escape::Escape; use crate::html::macro_expansion::ExpandedCode; use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary}; +use crate::html::render::span_map::Span; use crate::html::render::write_shared::write_shared; use crate::html::url_parts_builder::UrlPartsBuilder; use crate::html::{layout, sources, static_files}; @@ -139,7 +140,7 @@ pub(crate) struct SharedContext<'tcx> { /// Correspondence map used to link types used in the source code pages to allow to click on /// links to jump to the type's definition. - pub(crate) span_correspondence_map: FxHashMap<rustc_span::Span, LinkFromSrc>, + pub(crate) span_correspondence_map: FxHashMap<Span, LinkFromSrc>, pub(crate) expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>, /// The [`Cache`] used during rendering. pub(crate) cache: Cache, diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 88184c9611f..84d684e0c95 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -36,7 +36,7 @@ mod ordered_json; mod print_item; pub(crate) mod sidebar; mod sorted_template; -mod span_map; +pub(crate) mod span_map; mod type_layout; mod write_shared; @@ -48,18 +48,19 @@ use std::path::PathBuf; use std::{fs, str}; use askama::Template; +use indexmap::IndexMap; use itertools::Either; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; -use rustc_hir::attrs::{DeprecatedSince, Deprecation}; +use rustc_hir as hir; +use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation}; +use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ConstStability, Mutability, RustcVersion, StabilityLevel, StableSince}; use rustc_middle::ty::print::PrintTraitRefExt; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::symbol::{Symbol, sym}; use rustc_span::{BytePos, DUMMY_SP, FileName, RealFileName}; -use serde::ser::SerializeMap; -use serde::{Serialize, Serializer}; use tracing::{debug, info}; pub(crate) use self::context::*; @@ -75,7 +76,6 @@ use crate::html::escape::Escape; use crate::html::format::{ Ending, HrefError, PrintWithSpace, href, print_abi_with_space, print_constness_with_space, print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space, - write_str, }; use crate::html::markdown::{ HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine, @@ -603,7 +603,12 @@ impl AllTypes { } fmt::from_fn(|f| { - f.write_str("<h1>List of all items</h1>")?; + f.write_str( + "<div class=\"main-heading\">\ + <h1>List of all items</h1>\ + <rustdoc-toolbar></rustdoc-toolbar>\ + </div>", + )?; // Note: print_entries does not escape the title, because we know the current set of titles // doesn't require escaping. print_entries(&self.structs, ItemSection::Structs).fmt(f)?; @@ -1312,43 +1317,6 @@ fn render_assoc_item( }) } -struct CodeAttribute(String); - -fn render_code_attribute(prefix: &str, code_attr: CodeAttribute, w: &mut impl fmt::Write) { - write!( - w, - "<div class=\"code-attribute\">{prefix}{attr}</div>", - prefix = prefix, - attr = code_attr.0 - ) - .unwrap(); -} - -// When an attribute is rendered inside a <code> tag, it is formatted using -// a div to produce a newline after it. -fn render_attributes_in_code( - w: &mut impl fmt::Write, - it: &clean::Item, - prefix: &str, - cx: &Context<'_>, -) { - for attr in it.attributes(cx.tcx(), cx.cache()) { - render_code_attribute(prefix, CodeAttribute(attr), w); - } -} - -/// used for type aliases to only render their `repr` attribute. -fn render_repr_attributes_in_code( - w: &mut impl fmt::Write, - cx: &Context<'_>, - def_id: DefId, - item_type: ItemType, -) { - if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) { - render_code_attribute("", CodeAttribute(repr), w); - } -} - #[derive(Copy, Clone)] enum AssocItemLink<'a> { Anchor(Option<&'a str>), @@ -1509,12 +1477,10 @@ fn render_assoc_items_inner( ) } }; - let mut impls_buf = String::new(); - for i in &non_trait { - write_str( - &mut impls_buf, - format_args!( - "{}", + let impls_buf = fmt::from_fn(|f| { + non_trait + .iter() + .map(|i| { render_impl( cx, i, @@ -1530,9 +1496,11 @@ fn render_assoc_items_inner( toggle_open_by_default: true, }, ) - ), - ); - } + }) + .joined("", f) + }) + .to_string(); + if !impls_buf.is_empty() { write!( w, @@ -1684,91 +1652,85 @@ fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option<impl fmt: } fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { - let mut out = String::new(); - let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this"); let impls = cx.cache().impls.get(&did).expect("notable_traits_button already checked this"); - for i in impls { - let impl_ = i.inner_impl(); - if impl_.polarity != ty::ImplPolarity::Positive { - continue; - } - - if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) { - // Two different types might have the same did, - // without actually being the same. - continue; - } - if let Some(trait_) = &impl_.trait_ { - let trait_did = trait_.def_id(); - - if cx.cache().traits.get(&trait_did).is_some_and(|t| t.is_notable_trait(cx.tcx())) { - if out.is_empty() { - write_str( - &mut out, - format_args!( - "<h3>Notable traits for <code>{}</code></h3>\ - <pre><code>", - impl_.for_.print(cx) - ), - ); + let out = fmt::from_fn(|f| { + let mut notable_impls = impls + .iter() + .map(|impl_| impl_.inner_impl()) + .filter(|impl_| impl_.polarity == ty::ImplPolarity::Positive) + .filter(|impl_| { + // Two different types might have the same did, without actually being the same. + ty.is_doc_subtype_of(&impl_.for_, cx.cache()) + }) + .filter_map(|impl_| { + if let Some(trait_) = &impl_.trait_ + && let trait_did = trait_.def_id() + && let Some(trait_) = cx.cache().traits.get(&trait_did) + && trait_.is_notable_trait(cx.tcx()) + { + Some((impl_, trait_did)) + } else { + None } + }) + .peekable(); - write_str( - &mut out, - format_args!("<div class=\"where\">{}</div>", impl_.print(false, cx)), - ); - for it in &impl_.items { - if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { - let empty_set = FxIndexSet::default(); - let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); - write_str( - &mut out, - format_args!( - "<div class=\"where\"> {};</div>", - assoc_type( - it, - &tydef.generics, - &[], // intentionally leaving out bounds - Some(&tydef.type_), - src_link, - 0, - cx, - ) - ), - ); - } - } + let has_notable_impl = if let Some((impl_, _)) = notable_impls.peek() { + write!( + f, + "<h3>Notable traits for <code>{}</code></h3>\ + <pre><code>", + impl_.for_.print(cx) + )?; + true + } else { + false + }; + + for (impl_, trait_did) in notable_impls { + write!(f, "<div class=\"where\">{}</div>", impl_.print(false, cx))?; + for it in &impl_.items { + let clean::AssocTypeItem(tydef, ..) = &it.kind else { + continue; + }; + + let empty_set = FxIndexSet::default(); + let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); + + write!( + f, + "<div class=\"where\"> {};</div>", + assoc_type( + it, + &tydef.generics, + &[], // intentionally leaving out bounds + Some(&tydef.type_), + src_link, + 0, + cx, + ) + )?; } } - } - if out.is_empty() { - out.push_str("</code></pre>"); - } + + if !has_notable_impl { + f.write_str("</code></pre>")?; + } + + Ok(()) + }) + .to_string(); (format!("{:#}", ty.print(cx)), out) } fn notable_traits_json<'a>(tys: impl Iterator<Item = &'a clean::Type>, cx: &Context<'_>) -> String { - let mut mp: Vec<(String, String)> = tys.map(|ty| notable_traits_decl(ty, cx)).collect(); - mp.sort_by(|(name1, _html1), (name2, _html2)| name1.cmp(name2)); - struct NotableTraitsMap(Vec<(String, String)>); - impl Serialize for NotableTraitsMap { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(self.0.len()))?; - for item in &self.0 { - map.serialize_entry(&item.0, &item.1)?; - } - map.end() - } - } - serde_json::to_string(&NotableTraitsMap(mp)) - .expect("serialize (string, string) -> json object cannot fail") + let mut mp = tys.map(|ty| notable_traits_decl(ty, cx)).collect::<IndexMap<_, _>>(); + mp.sort_unstable_keys(); + serde_json::to_string(&mp).expect("serialize (string, string) -> json object cannot fail") } #[derive(Clone, Copy, Debug)] @@ -1842,27 +1804,19 @@ fn render_impl( document_item_info(cx, it, Some(parent)) .render_into(&mut info_buffer) .unwrap(); - write_str( - &mut doc_buffer, - format_args!("{}", document_full(item, cx, HeadingOffset::H5)), - ); + doc_buffer = document_full(item, cx, HeadingOffset::H5).to_string(); short_documented = false; } else { // In case the item isn't documented, // provide short documentation from the trait. - write_str( - &mut doc_buffer, - format_args!( - "{}", - document_short( - it, - cx, - link, - parent, - rendering_params.show_def_docs, - ) - ), - ); + doc_buffer = document_short( + it, + cx, + link, + parent, + rendering_params.show_def_docs, + ) + .to_string(); } } } else { @@ -1870,21 +1824,14 @@ fn render_impl( .render_into(&mut info_buffer) .unwrap(); if rendering_params.show_def_docs { - write_str( - &mut doc_buffer, - format_args!("{}", document_full(item, cx, HeadingOffset::H5)), - ); + doc_buffer = document_full(item, cx, HeadingOffset::H5).to_string(); short_documented = false; } } } else { - write_str( - &mut doc_buffer, - format_args!( - "{}", - document_short(item, cx, link, parent, rendering_params.show_def_docs) - ), - ); + doc_buffer = + document_short(item, cx, link, parent, rendering_params.show_def_docs) + .to_string(); } } let mut w = if short_documented && trait_.is_some() { @@ -2961,3 +2908,142 @@ fn render_call_locations<W: fmt::Write>( w.write_str("</div>") } + +fn render_attributes_in_code( + w: &mut impl fmt::Write, + item: &clean::Item, + prefix: &str, + cx: &Context<'_>, +) { + for attr in &item.attrs.other_attrs { + let hir::Attribute::Parsed(kind) = attr else { continue }; + let attr = match kind { + AttributeKind::LinkSection { name, .. } => { + Cow::Owned(format!("#[unsafe(link_section = {})]", Escape(&format!("{name:?}")))) + } + AttributeKind::NoMangle(..) => Cow::Borrowed("#[unsafe(no_mangle)]"), + AttributeKind::ExportName { name, .. } => { + Cow::Owned(format!("#[unsafe(export_name = {})]", Escape(&format!("{name:?}")))) + } + AttributeKind::NonExhaustive(..) => Cow::Borrowed("#[non_exhaustive]"), + _ => continue, + }; + render_code_attribute(prefix, attr.as_ref(), w); + } + + if let Some(def_id) = item.def_id() + && let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) + { + render_code_attribute(prefix, &repr, w); + } +} + +fn render_repr_attribute_in_code(w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId) { + if let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) { + render_code_attribute("", &repr, w); + } +} + +fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) { + write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>").unwrap(); +} + +/// Compute the *public* `#[repr]` of the item given by `DefId`. +/// +/// Read more about it here: +/// <https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type>. +fn repr_attribute<'tcx>( + tcx: TyCtxt<'tcx>, + cache: &Cache, + def_id: DefId, +) -> Option<Cow<'static, str>> { + let adt = match tcx.def_kind(def_id) { + DefKind::Struct | DefKind::Enum | DefKind::Union => tcx.adt_def(def_id), + _ => return None, + }; + let repr = adt.repr(); + + let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id); + let is_public_field = |field: &ty::FieldDef| { + (cache.document_private || field.vis.is_public()) && is_visible(field.did) + }; + + if repr.transparent() { + // The transparent repr is public iff the non-1-ZST field is public and visible or + // – in case all fields are 1-ZST fields — at least one field is public and visible. + let is_public = 'is_public: { + // `#[repr(transparent)]` can only be applied to structs and single-variant enums. + let var = adt.variant(rustc_abi::FIRST_VARIANT); // the first and only variant + + if !is_visible(var.def_id) { + break 'is_public false; + } + + // Side note: There can only ever be one or zero non-1-ZST fields. + let non_1zst_field = var.fields.iter().find(|field| { + let ty = ty::TypingEnv::post_analysis(tcx, field.did) + .as_query_input(tcx.type_of(field.did).instantiate_identity()); + tcx.layout_of(ty).is_ok_and(|layout| !layout.is_1zst()) + }); + + match non_1zst_field { + Some(field) => is_public_field(field), + None => var.fields.is_empty() || var.fields.iter().any(is_public_field), + } + }; + + // Since the transparent repr can't have any other reprs or + // repr modifiers beside it, we can safely return early here. + return is_public.then(|| "#[repr(transparent)]".into()); + } + + // Fast path which avoids looking through the variants and fields in + // the common case of no `#[repr]` or in the case of `#[repr(Rust)]`. + // FIXME: This check is not very robust / forward compatible! + if !repr.c() + && !repr.simd() + && repr.int.is_none() + && repr.pack.is_none() + && repr.align.is_none() + { + return None; + } + + // The repr is public iff all components are public and visible. + let is_public = adt + .variants() + .iter() + .all(|variant| is_visible(variant.def_id) && variant.fields.iter().all(is_public_field)); + if !is_public { + return None; + } + + let mut result = Vec::<Cow<'_, _>>::new(); + + if repr.c() { + result.push("C".into()); + } + if repr.simd() { + result.push("simd".into()); + } + if let Some(int) = repr.int { + let prefix = if int.is_signed() { 'i' } else { 'u' }; + let int = match int { + rustc_abi::IntegerType::Pointer(_) => format!("{prefix}size"), + rustc_abi::IntegerType::Fixed(int, _) => { + format!("{prefix}{}", int.size().bytes() * 8) + } + }; + result.push(int.into()); + } + + // Render modifiers last. + if let Some(pack) = repr.pack { + result.push(format!("packed({})", pack.bytes()).into()); + } + if let Some(align) = repr.align { + result.push(format!("align({})", align.bytes()).into()); + } + + (!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")).into()) +} diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index afa438f2596..adfc7481c73 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -21,7 +21,7 @@ use super::{ collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference, item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls, render_assoc_item, render_assoc_items, render_attributes_in_code, render_impl, - render_repr_attributes_in_code, render_rightside, render_stability_since_raw, + render_repr_attribute_in_code, render_rightside, render_stability_since_raw, render_stability_since_raw_with_extra, write_section_heading, }; use crate::clean; @@ -1555,7 +1555,7 @@ impl<'clean> DisplayEnum<'clean> { wrap_item(w, |w| { if is_type_alias { // For now the only attributes we render for type aliases are `repr` attributes. - render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum); + render_repr_attribute_in_code(w, cx, self.def_id); } else { render_attributes_in_code(w, it, "", cx); } @@ -2017,7 +2017,7 @@ impl<'a> DisplayStruct<'a> { wrap_item(w, |w| { if is_type_alias { // For now the only attributes we render for type aliases are `repr` attributes. - render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct); + render_repr_attribute_in_code(w, cx, self.def_id); } else { render_attributes_in_code(w, it, "", cx); } @@ -2371,7 +2371,7 @@ fn render_union( fmt::from_fn(move |mut f| { if is_type_alias { // For now the only attributes we render for type aliases are `repr` attributes. - render_repr_attributes_in_code(f, cx, def_id, ItemType::Union); + render_repr_attribute_in_code(f, cx, def_id); } else { render_attributes_in_code(f, it, "", cx); } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 81b38d157a8..253d9029468 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -6,6 +6,8 @@ use std::path::Path; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::find_attr; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::sym; @@ -239,6 +241,34 @@ impl SerializedSearchIndex { self.alias_pointers.push(alias_pointer); index } + /// Add potential search result to the database and return the row ID. + /// + /// The returned ID can be used to attach more data to the search result. + fn add_entry(&mut self, name: Symbol, entry_data: EntryData, desc: String) -> usize { + let fqp = if let Some(module_path_index) = entry_data.module_path { + let mut fqp = self.path_data[module_path_index].as_ref().unwrap().module_path.clone(); + fqp.push(Symbol::intern(&self.names[module_path_index])); + fqp.push(name); + fqp + } else { + vec![name] + }; + // If a path with the same name already exists, but no entry does, + // we can fill in the entry without having to allocate a new row ID. + // + // Because paths and entries both share the same index, using the same + // ID saves space by making the tree smaller. + if let Some(&other_path) = self.crate_paths_index.get(&(entry_data.ty, fqp)) + && self.entry_data[other_path].is_none() + && self.descs[other_path].is_empty() + { + self.entry_data[other_path] = Some(entry_data); + self.descs[other_path] = desc; + other_path + } else { + self.push(name.as_str().to_string(), None, Some(entry_data), desc, None, None, None) + } + } fn push_path(&mut self, name: String, path_data: PathData) -> usize { self.push(name, Some(path_data), None, String::new(), None, None, None) } @@ -1477,16 +1507,17 @@ pub(crate) fn build_index( if fqp.last() != Some(&item.name) { return None; } - let path = - if item.ty == ItemType::Macro && tcx.has_attr(defid, sym::macro_export) { - // `#[macro_export]` always exports to the crate root. - vec![tcx.crate_name(defid.krate)] - } else { - if fqp.len() < 2 { - return None; - } - fqp[..fqp.len() - 1].to_vec() - }; + let path = if item.ty == ItemType::Macro + && find_attr!(tcx.get_all_attrs(defid), AttributeKind::MacroExport { .. }) + { + // `#[macro_export]` always exports to the crate root. + vec![tcx.crate_name(defid.krate)] + } else { + if fqp.len() < 2 { + return None; + } + fqp[..fqp.len() - 1].to_vec() + }; if path == item.module_path { return None; } @@ -1532,10 +1563,9 @@ pub(crate) fn build_index( .as_ref() .map(|path| serialized_index.get_id_by_module_path(path)); - let new_entry_id = serialized_index.push( - item.name.as_str().to_string(), - None, - Some(EntryData { + let new_entry_id = serialized_index.add_entry( + item.name, + EntryData { ty: item.ty, parent: item.parent_idx, trait_parent: item.trait_parent_idx, @@ -1555,11 +1585,8 @@ pub(crate) fn build_index( None }, krate: crate_idx, - }), + }, item.desc.to_string(), - None, // filled in after all the types have been indexed - None, - None, ); // Aliases diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 8bc2e0bd957..bc9417b1bb1 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -2,19 +2,54 @@ use std::path::{Path, PathBuf}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; -use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{ - ExprKind, HirId, Item, ItemKind, Mod, Node, Pat, PatExpr, PatExprKind, PatKind, QPath, -}; +use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; +use rustc_hir::intravisit::{self, Visitor, VisitorExt}; +use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::hygiene::MacroKind; -use rustc_span::{BytePos, ExpnKind, Span}; +use rustc_span::{BytePos, ExpnKind}; use crate::clean::{self, PrimitiveType, rustc_span}; use crate::html::sources; +/// This is a stripped down version of [`rustc_span::Span`] that only contains the start and end byte positions of the span. +/// +/// Profiling showed that the `Span` interner was taking up a lot of the run-time when highlighting, and since we +/// never actually use the context and parent that are stored in a normal `Span`, we can replace its usages with this +/// one, which is much cheaper to construct. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Span { + lo: BytePos, + hi: BytePos, +} + +impl From<rustc_span::Span> for Span { + fn from(value: rustc_span::Span) -> Self { + Self { lo: value.lo(), hi: value.hi() } + } +} + +impl Span { + pub(crate) fn lo(self) -> BytePos { + self.lo + } + + pub(crate) fn hi(self) -> BytePos { + self.hi + } + + pub(crate) fn with_lo(self, lo: BytePos) -> Self { + Self { lo, hi: self.hi() } + } + + pub(crate) fn with_hi(self, hi: BytePos) -> Self { + Self { lo: self.lo(), hi } + } +} + +pub(crate) const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0) }; + /// This enum allows us to store two different kinds of information: /// /// In case the `span` definition comes from the same crate, we can simply get the `span` and use @@ -67,7 +102,7 @@ struct SpanMapVisitor<'tcx> { impl SpanMapVisitor<'_> { /// This function is where we handle `hir::Path` elements and add them into the "span map". - fn handle_path(&mut self, path: &rustc_hir::Path<'_>) { + fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) { match path.res { // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`. // Would be nice to support them too alongside the other `DefKind` @@ -79,28 +114,41 @@ impl SpanMapVisitor<'_> { LinkFromSrc::External(def_id) }; // In case the path ends with generics, we remove them from the span. - let span = path - .segments - .last() - .map(|last| { - // In `use` statements, the included item is not in the path segments. - // However, it doesn't matter because you can't have generics on `use` - // statements. - if path.span.contains(last.ident.span) { - path.span.with_hi(last.ident.span.hi()) - } else { - path.span - } - }) - .unwrap_or(path.span); - self.matches.insert(span, link); + let span = if only_use_last_segment + && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) + { + path_span + } else { + path.segments + .last() + .map(|last| { + // In `use` statements, the included item is not in the path segments. + // However, it doesn't matter because you can't have generics on `use` + // statements. + if path.span.contains(last.ident.span) { + path.span.with_hi(last.ident.span.hi()) + } else { + path.span + } + }) + .unwrap_or(path.span) + }; + self.matches.insert(span.into(), link); } Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => { - self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span))); + let path_span = if only_use_last_segment + && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span) + { + path_span + } else { + path.span + }; + self.matches.insert(path_span.into(), LinkFromSrc::Local(clean::Span::new(span))); } Res::PrimTy(p) => { // FIXME: Doesn't handle "path-like" primitives like arrays or tuples. - self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p))); + self.matches + .insert(path.span.into(), LinkFromSrc::Primitive(PrimitiveType::from(p))); } Res::Err => {} _ => {} @@ -117,7 +165,7 @@ impl SpanMapVisitor<'_> { if cspan.inner().is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE { return; } - self.matches.insert(span, LinkFromSrc::Doc(item.owner_id.to_def_id())); + self.matches.insert(span.into(), LinkFromSrc::Doc(item.owner_id.to_def_id())); } } @@ -128,7 +176,7 @@ impl SpanMapVisitor<'_> { /// so, we loop until we find the macro definition by using `outer_expn_data` in a loop. /// Finally, we get the information about the macro itself (`span` if "local", `DefId` /// otherwise) and store it inside the span map. - fn handle_macro(&mut self, span: Span) -> bool { + fn handle_macro(&mut self, span: rustc_span::Span) -> bool { if !span.from_expansion() { return false; } @@ -166,7 +214,7 @@ impl SpanMapVisitor<'_> { // The "call_site" includes the whole macro with its "arguments". We only want // the macro name. let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32)); - self.matches.insert(new_span, link_from_src); + self.matches.insert(new_span.into(), link_from_src); true } @@ -189,31 +237,23 @@ impl SpanMapVisitor<'_> { self.matches.insert(span, link); } } +} - fn handle_pat(&mut self, p: &Pat<'_>) { - let mut check_qpath = |qpath, hir_id| match qpath { - QPath::TypeRelative(_, path) if matches!(path.res, Res::Err) => { - self.infer_id(path.hir_id, Some(hir_id), qpath.span()); - } - QPath::Resolved(_, path) => self.handle_path(path), - _ => {} - }; - match p.kind { - PatKind::Binding(_, _, _, Some(p)) => self.handle_pat(p), - PatKind::Struct(qpath, _, _) | PatKind::TupleStruct(qpath, _, _) => { - check_qpath(qpath, p.hir_id) - } - PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, .. }) => { - check_qpath(*qpath, *hir_id) - } - PatKind::Or(pats) => { - for pat in pats { - self.handle_pat(pat); - } - } - _ => {} +// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without +// panicking. +fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> { + for (_, node) in tcx.hir_parent_iter(hir_id) { + // FIXME: associated type impl items don't have an associated body, so we don't handle + // them currently. + if let Node::ImplItem(impl_item) = node + && matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_)) + { + return None; + } else if let Some((def_id, _)) = node.associated_body() { + return Some(def_id); } } + None } impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { @@ -227,15 +267,45 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { if self.handle_macro(path.span) { return; } - self.handle_path(path); + self.handle_path(path, false); intravisit::walk_path(self, path); } - fn visit_pat(&mut self, p: &Pat<'tcx>) { - self.handle_pat(p); + fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) { + match *qpath { + QPath::TypeRelative(qself, path) => { + if matches!(path.res, Res::Err) { + let tcx = self.tcx; + if let Some(body_id) = hir_enclosing_body_owner(tcx, id) { + let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id()); + let path = rustc_hir::Path { + // We change the span to not include parens. + span: path.ident.span, + res: typeck_results.qpath_res(qpath, id), + segments: &[], + }; + self.handle_path(&path, false); + } + } else { + self.infer_id(path.hir_id, Some(id), path.ident.span.into()); + } + + rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself)); + self.visit_path_segment(path); + } + QPath::Resolved(maybe_qself, path) => { + self.handle_path(path, true); + + rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself); + if !self.handle_macro(path.span) { + intravisit::walk_path(self, path); + } + } + _ => {} + } } - fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) { + fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: HirId) { // To make the difference between "mod foo {}" and "mod foo;". In case we "import" another // file, we want to link to it. Otherwise no need to create a link. if !span.overlaps(m.spans.inner_span) { @@ -243,8 +313,10 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { // name only and not all the "mod foo;". if let Node::Item(item) = self.tcx.hir_node(id) { let (ident, _) = item.expect_mod(); - self.matches - .insert(ident.span, LinkFromSrc::Local(clean::Span::new(m.spans.inner_span))); + self.matches.insert( + ident.span.into(), + LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)), + ); } } else { // If it's a "mod foo {}", we want to look to its documentation page. @@ -256,9 +328,9 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) { match expr.kind { ExprKind::MethodCall(segment, ..) => { - self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span) + self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span.into()) } - ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span), + ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span.into()), _ => { if self.handle_macro(expr.span) { // We don't want to go deeper into the macro. diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index e37a5246a76..3a1db805d01 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -386,8 +386,13 @@ impl CratesIndexPart { let layout = &cx.shared.layout; let style_files = &cx.shared.style_files; const DELIMITER: &str = "\u{FFFC}"; // users are being naughty if they have this - let content = - format!("<h1>List of all crates</h1><ul class=\"all-items\">{DELIMITER}</ul>"); + let content = format!( + "<div class=\"main-heading\">\ + <h1>List of all crates</h1>\ + <rustdoc-toolbar></rustdoc-toolbar>\ + </div>\ + <ul class=\"all-items\">{DELIMITER}</ul>" + ); let template = layout::render(layout, &page, "", content, style_files); SortedTemplate::from_template(&template, DELIMITER) .expect("Object Replacement Character (U+FFFC) should not appear in the --index-page") diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index 9c5518a780e..c79f63fbc20 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -348,7 +348,12 @@ pub(crate) fn print_src( highlight::write_code( fmt, s, - Some(highlight::HrefContext { context, file_span, root_path, current_href }), + Some(highlight::HrefContext { + context, + file_span: file_span.into(), + root_path, + current_href, + }), Some(decoration_info), Some(line_info), ); diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 75febd6f737..3ea9de381ec 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -2227,11 +2227,18 @@ function preLoadCss(cssUrl) { }); }()); -// This section is a bugfix for firefox: when copying text with `user-select: none`, it adds -// extra backline characters. + +// Workaround for browser-specific bugs when copying code snippets. +// +// * In Firefox, copying text that includes elements with `user-select: none` +// inserts extra blank lines. +// - Firefox issue: https://bugzilla.mozilla.org/show_bug.cgi?id=1273836 +// - Rust issue: https://github.com/rust-lang/rust/issues/141464 // -// Rustdoc issue: Workaround for https://github.com/rust-lang/rust/issues/141464 -// Firefox issue: https://bugzilla.mozilla.org/show_bug.cgi?id=1273836 +// * In Chromium-based browsers, `document.getSelection()` includes elements +// with `user-select: none`, causing unwanted line numbers to be copied. +// - Chromium issue: https://issues.chromium.org/issues/446539520 +// - Rust issue: https://github.com/rust-lang/rust/issues/146816 (function() { document.body.addEventListener("copy", event => { let target = nonnull(event.target); @@ -2248,9 +2255,13 @@ function preLoadCss(cssUrl) { if (!isInsideCode) { return; } - const selection = document.getSelection(); - // @ts-expect-error - nonnull(event.clipboardData).setData("text/plain", selection.toString()); + const selection = nonnull(document.getSelection()); + const text = Array.from({ length: selection.rangeCount }, (_, i) => { + const fragment = selection.getRangeAt(i).cloneContents(); + fragment.querySelectorAll("[data-nosnippet]").forEach(el => el.remove()); + return fragment.textContent; + }).join(""); + nonnull(event.clipboardData).setData("text/plain", text); event.preventDefault(); }); }()); diff --git a/src/librustdoc/html/static/js/stringdex.js b/src/librustdoc/html/static/js/stringdex.js index 5bdc81e330e..6299576d564 100644 --- a/src/librustdoc/html/static/js/stringdex.js +++ b/src/librustdoc/html/static/js/stringdex.js @@ -1108,22 +1108,39 @@ function loadDatabase(hooks) { const id2 = id1 + ((nodeid[4] << 8) | nodeid[5]); leaves = RoaringBitmap.makeSingleton(id1) .union(RoaringBitmap.makeSingleton(id2)); + } else if (!isWhole && (nodeid[0] & 0xf0) === 0x80) { + const id1 = ((nodeid[0] & 0x0f) << 16) | (nodeid[1] << 8) | nodeid[2]; + const id2 = id1 + ((nodeid[3] << 4) | ((nodeid[4] >> 4) & 0x0f)); + const id3 = id2 + (((nodeid[4] & 0x0f) << 8) | nodeid[5]); + leaves = RoaringBitmap.makeSingleton(id1) + .union(RoaringBitmap.makeSingleton(id2)) + .union(RoaringBitmap.makeSingleton(id3)); } else { leaves = RoaringBitmap.makeSingleton( (nodeid[2] << 24) | (nodeid[3] << 16) | (nodeid[4] << 8) | nodeid[5], ); } - const data = (nodeid[0] & 0x20) !== 0 ? - Uint8Array.of(((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)) : - EMPTY_UINT8; - newPromise = Promise.resolve(new PrefixSearchTree( - EMPTY_SEARCH_TREE_BRANCHES, - EMPTY_SEARCH_TREE_BRANCHES, - data, - isWhole ? leaves : EMPTY_BITMAP, - isWhole ? EMPTY_BITMAP : leaves, - )); + if (isWhole) { + const data = (nodeid[0] & 0x20) !== 0 ? + Uint8Array.of(((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)) : + EMPTY_UINT8; + newPromise = Promise.resolve(new PrefixSearchTree( + EMPTY_SEARCH_TREE_BRANCHES, + EMPTY_SEARCH_TREE_BRANCHES, + data, + leaves, + EMPTY_BITMAP, + )); + } else { + const data = (nodeid[0] & 0xf0) === 0x80 ? 0 : ( + ((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)); + newPromise = Promise.resolve(new SuffixSearchTree( + EMPTY_SEARCH_TREE_BRANCHES, + data, + leaves, + )); + } } else { const hashHex = makeHexFromUint8Array(nodeid); newPromise = new Promise((resolve, reject) => { @@ -2748,6 +2765,7 @@ function loadDatabase(hooks) { // because that's the canonical, hashed version of the data let compression_tag = input[i]; const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0; + let no_leaves_flag; if (compression_tag > 1) { // compressed node const is_long_compressed = (compression_tag & 0x04) !== 0; @@ -2759,7 +2777,8 @@ function loadDatabase(hooks) { compression_tag |= input[i] << 16; i += 1; } - let dlen = input[i]; + let dlen = input[i] & 0x7F; + no_leaves_flag = input[i] & 0x80; i += 1; if (is_data_compressed) { data = data_history[data_history.length - dlen - 1]; @@ -2786,10 +2805,15 @@ function loadDatabase(hooks) { let whole; let suffix; if (is_pure_suffixes_only_node) { - suffix = input[i] === 0 ? - EMPTY_BITMAP1 : - new RoaringBitmap(input, i); - i += suffix.consumed_len_bytes; + if (no_leaves_flag) { + whole = EMPTY_BITMAP; + suffix = EMPTY_BITMAP; + } else { + suffix = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += suffix.consumed_len_bytes; + } tree = new SuffixSearchTree( branches, dlen, @@ -2807,7 +2831,7 @@ function loadDatabase(hooks) { let ci = 0; canonical[ci] = 1; ci += 1; - canonical[ci] = dlen; + canonical[ci] = dlen | no_leaves_flag; ci += 1; canonical[ci] = input[coffset]; // suffix child count ci += 1; @@ -2821,10 +2845,9 @@ function loadDatabase(hooks) { } siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash); } else { - if (input[i] === 0xff) { + if (no_leaves_flag) { whole = EMPTY_BITMAP; - suffix = EMPTY_BITMAP1; - i += 1; + suffix = EMPTY_BITMAP; } else { whole = input[i] === 0 ? EMPTY_BITMAP1 : @@ -2856,7 +2879,7 @@ function loadDatabase(hooks) { let ci = 0; canonical[ci] = 0; ci += 1; - canonical[ci] = dlen; + canonical[ci] = dlen | no_leaves_flag; ci += 1; canonical.set(data, ci); ci += data.length; @@ -2880,9 +2903,11 @@ function loadDatabase(hooks) { } hash[2] &= 0x7f; } else { + i += 1; // uncompressed node - const dlen = input [i + 1]; - i += 2; + const dlen = input[i] & 0x7F; + no_leaves_flag = input[i] & 0x80; + i += 1; if (dlen === 0 || is_pure_suffixes_only_node) { data = EMPTY_UINT8; } else { @@ -2897,16 +2922,15 @@ function loadDatabase(hooks) { i += branches_consumed_len_bytes; let whole; let suffix; - if (is_pure_suffixes_only_node) { + if (no_leaves_flag) { + whole = EMPTY_BITMAP; + suffix = EMPTY_BITMAP; + } else if (is_pure_suffixes_only_node) { whole = EMPTY_BITMAP; suffix = input[i] === 0 ? EMPTY_BITMAP1 : new RoaringBitmap(input, i); i += suffix.consumed_len_bytes; - } else if (input[i] === 0xff) { - whole = EMPTY_BITMAP; - suffix = EMPTY_BITMAP; - i += 1; } else { whole = input[i] === 0 ? EMPTY_BITMAP1 : diff --git a/src/librustdoc/html/templates/type_layout.html b/src/librustdoc/html/templates/type_layout.html index 0034552bdd3..49153d58fe9 100644 --- a/src/librustdoc/html/templates/type_layout.html +++ b/src/librustdoc/html/templates/type_layout.html @@ -65,5 +65,10 @@ <strong>Note:</strong> Encountered an error during type layout; {#+ #} the type's layout depended on the type's layout itself. {# #} </p> + {% when Err(LayoutError::InvalidSimd {..}) %} + <p> {# #} + <strong>Note:</strong> Encountered an error during type layout; {#+ #} + the vector type had zero elements or too many elements. {# #} + </p> {% endmatch %} </div> {# #} diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 6fe94f9d291..779e26c7b0f 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -912,12 +912,8 @@ fn maybe_from_hir_attr( hir::Attribute::Parsed(kind) => kind, hir::Attribute::Unparsed(_) => { - return Some(if attr.has_name(sym::macro_export) { - Attribute::MacroExport - // FIXME: We should handle `#[doc(hidden)]`. - } else { - other_attr(tcx, attr) - }); + // FIXME: We should handle `#[doc(hidden)]`. + return Some(other_attr(tcx, attr)); } }; @@ -925,6 +921,7 @@ fn maybe_from_hir_attr( AK::Deprecation { .. } => return None, // Handled separately into Item::deprecation. AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"), + AK::MacroExport { .. } => Attribute::MacroExport, AK::MustUse { reason, span: _ } => { Attribute::MustUse { reason: reason.map(|s| s.to_string()) } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 9871066b9eb..d7ffb25f8bd 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -1,5 +1,4 @@ // tidy-alphabetical-start -#![cfg_attr(bootstrap, feature(round_char_boundary))] #![doc( html_root_url = "https://doc.rust-lang.org/nightly/", html_playground_url = "https://play.rust-lang.org/" @@ -10,9 +9,11 @@ #![feature(box_patterns)] #![feature(debug_closure_helpers)] #![feature(file_buffered)] +#![feature(formatting_options)] #![feature(if_let_guard)] #![feature(iter_advance_by)] #![feature(iter_intersperse)] +#![feature(iter_order_by)] #![feature(rustc_private)] #![feature(test)] #![warn(rustc::internal)] @@ -835,8 +836,10 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { config::InputMode::NoInputMergeFinalize => { return wrap_return( dcx, - run_merge_finalize(render_options) - .map_err(|e| format!("could not write merged cross-crate info: {e}")), + rustc_span::create_session_globals_then(options.edition, &[], None, || { + run_merge_finalize(render_options) + .map_err(|e| format!("could not write merged cross-crate info: {e}")) + }), ); } }; diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 0da42f38251..79d74c3c4eb 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -130,6 +130,7 @@ impl Res { DefKind::Static { .. } => "static", DefKind::Field => "field", DefKind::Variant | DefKind::Ctor(..) => "variant", + DefKind::TyAlias => "tyalias", // Now handle things that don't have a specific disambiguator _ => match kind .ns() @@ -1708,6 +1709,7 @@ impl Disambiguator { "value" => NS(Namespace::ValueNS), "macro" => NS(Namespace::MacroNS), "prim" | "primitive" => Primitive, + "tyalias" | "typealias" => Kind(DefKind::TyAlias), _ => return Err((format!("unknown disambiguator `{prefix}`"), 0..idx)), }; diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index da09117b1bb..136ff258048 100644 --- a/src/librustdoc/passes/lint/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -364,6 +364,7 @@ impl TagParser { } else { if !self.tag_name.is_empty() { self.in_attrs = true; + // range of the entire tag within dox let mut r = Range { start: range.start + start_pos, end: range.start + pos }; if c == '>' { // In case we have a tag without attribute, we can consider the span to @@ -381,7 +382,7 @@ impl TagParser { for (new_pos, c) in text[pos..].char_indices() { if !c.is_whitespace() { if c == '>' { - r.end = range.start + new_pos + 1; + r.end = range.start + pos + new_pos + 1; found = true; } else if c == '<' { self.handle_lt_in_tag(range.clone(), pos + new_pos, f); diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index 475d05b7d0e..f45df8d2d0d 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -77,11 +77,11 @@ pub(crate) enum Condition { pub(crate) const PASSES: &[Pass] = &[ CHECK_DOC_CFG, CHECK_DOC_TEST_VISIBILITY, + PROPAGATE_DOC_CFG, STRIP_ALIASED_NON_LOCAL, STRIP_HIDDEN, STRIP_PRIVATE, STRIP_PRIV_IMPORTS, - PROPAGATE_DOC_CFG, PROPAGATE_STABILITY, COLLECT_INTRA_DOC_LINKS, COLLECT_TRAIT_IMPLS, @@ -94,11 +94,11 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[ ConditionalPass::always(COLLECT_TRAIT_IMPLS), ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY), ConditionalPass::always(CHECK_DOC_CFG), + ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), ConditionalPass::always(STRIP_ALIASED_NON_LOCAL), ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate), - ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), ConditionalPass::always(PROPAGATE_DOC_CFG), ConditionalPass::always(PROPAGATE_STABILITY), ConditionalPass::always(RUN_LINTS), diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index eddafa9ba8e..d5b20f2b941 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -1,12 +1,12 @@ //! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items. -use std::sync::Arc; +use rustc_ast::token::{Token, TokenKind}; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_hir::{AttrArgs, Attribute}; +use rustc_span::symbol::sym; -use rustc_hir::def_id::LocalDefId; - -use crate::clean::cfg::Cfg; use crate::clean::inline::{load_attrs, merge_attrs}; -use crate::clean::{Crate, Item, ItemKind}; +use crate::clean::{CfgInfo, Crate, Item, ItemKind}; use crate::core::DocContext; use crate::fold::DocFolder; use crate::passes::Pass; @@ -18,80 +18,112 @@ pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass { }; pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate { - CfgPropagator { parent_cfg: None, parent: None, cx }.fold_crate(cr) + if cx.tcx.features().doc_cfg() { + CfgPropagator { cx, cfg_info: CfgInfo::default() }.fold_crate(cr) + } else { + cr + } } struct CfgPropagator<'a, 'tcx> { - parent_cfg: Option<Arc<Cfg>>, - parent: Option<LocalDefId>, cx: &'a mut DocContext<'tcx>, + cfg_info: CfgInfo, } -impl CfgPropagator<'_, '_> { - // Some items need to merge their attributes with their parents' otherwise a few of them - // (mostly `cfg` ones) will be missing. - fn merge_with_parent_attributes(&mut self, item: &mut Item) { - let check_parent = match &item.kind { - // impl blocks can be in different modules with different cfg and we need to get them - // as well. - ItemKind::ImplItem(_) => false, - kind if kind.is_non_assoc() => true, - _ => return, - }; +/// Returns true if the provided `token` is a `cfg` ident. +fn is_cfg_token(token: &TokenTree) -> bool { + // We only keep `doc(cfg)` items. + matches!(token, TokenTree::Token(Token { kind: TokenKind::Ident(sym::cfg, _,), .. }, _,),) +} - let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) else { - return; - }; +/// We only want to keep `#[cfg()]` and `#[doc(cfg())]` attributes so we rebuild a vec of +/// `TokenTree` with only the tokens we're interested into. +fn filter_non_cfg_tokens_from_list(args_tokens: &TokenStream) -> Vec<TokenTree> { + let mut tokens = Vec::with_capacity(args_tokens.len()); + let mut skip_next_delimited = false; + for token in args_tokens.iter() { + match token { + TokenTree::Delimited(..) => { + if !skip_next_delimited { + tokens.push(token.clone()); + } + skip_next_delimited = false; + } + token if is_cfg_token(token) => { + skip_next_delimited = false; + tokens.push(token.clone()); + } + _ => { + skip_next_delimited = true; + } + } + } + tokens +} - if check_parent { - let expected_parent = self.cx.tcx.opt_local_parent(def_id); - // If parents are different, it means that `item` is a reexport and we need - // to compute the actual `cfg` by iterating through its "real" parents. - if self.parent.is_some() && self.parent == expected_parent { - return; +/// This function goes through the attributes list (`new_attrs`) and extract the `cfg` tokens from +/// it and put them into `attrs`. +fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute]) { + for attr in new_attrs { + if attr.is_doc_comment() { + continue; + } + let mut attr = attr.clone(); + if let Attribute::Unparsed(ref mut normal) = attr + && let [ident] = &*normal.path.segments + { + let ident = ident.name; + if ident == sym::doc + && let AttrArgs::Delimited(args) = &mut normal.args + { + let tokens = filter_non_cfg_tokens_from_list(&args.tokens); + args.tokens = TokenStream::new(tokens); + attrs.push(attr); + } else if ident == sym::cfg_trace { + // If it's a `cfg()` attribute, we keep it. + attrs.push(attr); } } + } +} +impl CfgPropagator<'_, '_> { + // Some items need to merge their attributes with their parents' otherwise a few of them + // (mostly `cfg` ones) will be missing. + fn merge_with_parent_attributes(&mut self, item: &mut Item) { let mut attrs = Vec::new(); - let mut next_def_id = def_id; - while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) { - attrs.extend_from_slice(load_attrs(self.cx, parent_def_id.to_def_id())); - next_def_id = parent_def_id; + // We only need to merge an item attributes with its parent's in case it's an impl as an + // impl might not be defined in the same module as the item it implements. + // + // Otherwise, `cfg_info` already tracks everything we need so nothing else to do! + if matches!(item.kind, ItemKind::ImplItem(_)) + && let Some(mut next_def_id) = item.item_id.as_local_def_id() + { + while let Some(parent_def_id) = self.cx.tcx.opt_local_parent(next_def_id) { + let x = load_attrs(self.cx, parent_def_id.to_def_id()); + add_only_cfg_attributes(&mut attrs, x); + next_def_id = parent_def_id; + } } - let (_, cfg) = - merge_attrs(self.cx, item.attrs.other_attrs.as_slice(), Some((&attrs, None))); + let (_, cfg) = merge_attrs( + self.cx, + item.attrs.other_attrs.as_slice(), + Some((&attrs, None)), + &mut self.cfg_info, + ); item.inner.cfg = cfg; } } impl DocFolder for CfgPropagator<'_, '_> { fn fold_item(&mut self, mut item: Item) -> Option<Item> { - let old_parent_cfg = self.parent_cfg.clone(); + let old_cfg_info = self.cfg_info.clone(); self.merge_with_parent_attributes(&mut item); - let new_cfg = match (self.parent_cfg.take(), item.inner.cfg.take()) { - (None, None) => None, - (Some(rc), None) | (None, Some(rc)) => Some(rc), - (Some(mut a), Some(b)) => { - let b = Arc::try_unwrap(b).unwrap_or_else(|rc| Cfg::clone(&rc)); - *Arc::make_mut(&mut a) &= b; - Some(a) - } - }; - self.parent_cfg = new_cfg.clone(); - item.inner.cfg = new_cfg; - - let old_parent = - if let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) { - self.parent.replace(def_id) - } else { - self.parent.take() - }; let result = self.fold_item_recur(item); - self.parent_cfg = old_parent_cfg; - self.parent = old_parent; + self.cfg_info = old_cfg_info; Some(result) } diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs index 3388ae46f05..525d05b6a98 100644 --- a/src/librustdoc/passes/strip_hidden.rs +++ b/src/librustdoc/passes/strip_hidden.rs @@ -2,9 +2,10 @@ use std::mem; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; +use rustc_hir::find_attr; use rustc_middle::ty::TyCtxt; -use rustc_span::symbol::sym; use tracing::debug; use crate::clean::utils::inherits_doc_hidden; @@ -114,7 +115,7 @@ impl DocFolder for Stripper<'_, '_> { // If the macro has the `#[macro_export]` attribute, it means it's accessible at the // crate level so it should be handled differently. clean::MacroItem(..) => { - i.attrs.other_attrs.iter().any(|attr| attr.has_name(sym::macro_export)) + find_attr!(&i.attrs.other_attrs, AttributeKind::MacroExport { .. }) } _ => false, }; diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index b2e4b594375..dc9889cec21 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -5,10 +5,11 @@ use std::mem; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir as hir; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def::{DefKind, MacroKinds, Res}; use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet}; use rustc_hir::intravisit::{Visitor, walk_body, walk_item}; -use rustc_hir::{CRATE_HIR_ID, Node}; +use rustc_hir::{Node, find_attr}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::Span; @@ -16,7 +17,6 @@ use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; use rustc_span::symbol::{Symbol, kw, sym}; use tracing::debug; -use crate::clean::cfg::Cfg; use crate::clean::utils::{inherits_doc_hidden, should_ignore_res}; use crate::clean::{NestedAttributesExt, hir_attr_lists, reexport_chain}; use crate::core; @@ -166,7 +166,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { if !child.reexport_chain.is_empty() && let Res::Def(DefKind::Macro(_), def_id) = child.res && let Some(local_def_id) = def_id.as_local() - && self.cx.tcx.has_attr(def_id, sym::macro_export) + && find_attr!(self.cx.tcx.get_all_attrs(def_id), AttributeKind::MacroExport { .. }) && inserted.insert(def_id) { let item = self.cx.tcx.hir_expect_item(local_def_id); @@ -177,32 +177,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { } } - self.cx.cache.hidden_cfg = self - .cx - .tcx - .hir_attrs(CRATE_HIR_ID) - .iter() - .filter(|attr| attr.has_name(sym::doc)) - .flat_map(|attr| attr.meta_item_list().into_iter().flatten()) - .filter(|attr| attr.has_name(sym::cfg_hide)) - .flat_map(|attr| { - attr.meta_item_list() - .unwrap_or(&[]) - .iter() - .filter_map(|attr| { - Cfg::parse(attr) - .map_err(|e| self.cx.sess().dcx().span_err(e.span, e.msg)) - .ok() - }) - .collect::<Vec<_>>() - }) - .chain([ - Cfg::Cfg(sym::test, None), - Cfg::Cfg(sym::doc, None), - Cfg::Cfg(sym::doctest, None), - ]) - .collect(); - self.cx.cache.exact_paths = self.exact_paths; top_level_module } @@ -406,7 +380,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { || match item.kind { hir::ItemKind::Impl(..) => true, hir::ItemKind::Macro(_, _, _) => { - self.cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) + find_attr!(self.cx.tcx.get_all_attrs(item.owner_id.def_id), AttributeKind::MacroExport{..}) } _ => false, } @@ -524,7 +498,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let def_id = item.owner_id.to_def_id(); let is_macro_2_0 = !macro_def.macro_rules; - let nonexported = !tcx.has_attr(def_id, sym::macro_export); + let nonexported = + !find_attr!(tcx.get_all_attrs(def_id), AttributeKind::MacroExport { .. }); if is_macro_2_0 || nonexported || self.inlining { self.add_to_current_mod(item, renamed, import_id); |
