diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
commit | 9835e2ae736235810b4ea1c162ca5e65c547e770 (patch) | |
tree | 3fcebf40ed70e581d776a8a4c65923e8ec20e026 /src/librustdoc/html | |
parent | Releasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff) | |
download | rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip |
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/librustdoc/html')
-rw-r--r-- | src/librustdoc/html/format.rs | 49 | ||||
-rw-r--r-- | src/librustdoc/html/highlight.rs | 14 | ||||
-rw-r--r-- | src/librustdoc/html/markdown.rs | 136 | ||||
-rw-r--r-- | src/librustdoc/html/render/context.rs | 19 | ||||
-rw-r--r-- | src/librustdoc/html/render/mod.rs | 77 | ||||
-rw-r--r-- | src/librustdoc/html/render/print_item.rs | 269 | ||||
-rw-r--r-- | src/librustdoc/html/render/search_index.rs | 40 | ||||
-rw-r--r-- | src/librustdoc/html/render/type_layout.rs | 86 | ||||
-rw-r--r-- | src/librustdoc/html/sources.rs | 2 | ||||
-rw-r--r-- | src/librustdoc/html/static/css/rustdoc.css | 4 | ||||
-rw-r--r-- | src/librustdoc/html/static/css/settings.css | 10 | ||||
-rw-r--r-- | src/librustdoc/html/static/js/externs.js | 5 | ||||
-rw-r--r-- | src/librustdoc/html/static/js/main.js | 15 | ||||
-rw-r--r-- | src/librustdoc/html/static/js/search.js | 424 | ||||
-rw-r--r-- | src/librustdoc/html/static/js/settings.js | 19 | ||||
-rw-r--r-- | src/librustdoc/html/static/js/source-script.js | 2 | ||||
-rw-r--r-- | src/librustdoc/html/templates/item_union.html | 8 | ||||
-rw-r--r-- | src/librustdoc/html/templates/type_layout.html | 58 | ||||
-rw-r--r-- | src/librustdoc/html/templates/type_layout_size.html | 12 |
19 files changed, 731 insertions, 518 deletions
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 1b445b898..d963d6092 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -167,7 +167,7 @@ pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>( display_fn(move |f| { let mut bounds_dup = FxHashSet::default(); - for (i, bound) in bounds.iter().filter(|b| bounds_dup.insert(b.clone())).enumerate() { + for (i, bound) in bounds.iter().filter(|b| bounds_dup.insert(*b)).enumerate() { if i > 0 { f.write_str(" + ")?; } @@ -439,6 +439,7 @@ impl clean::GenericBound { let modifier_str = match modifier { hir::TraitBoundModifier::None => "", hir::TraitBoundModifier::Maybe => "?", + hir::TraitBoundModifier::Negative => "!", // ~const is experimental; do not display those bounds in rustdoc hir::TraitBoundModifier::MaybeConst => "", }; @@ -1115,14 +1116,17 @@ fn fmt_type<'cx>( ref trait_, should_show_cast, }) => { + // 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 should_show_cast { + if let Some(trait_) = trait_ && should_show_cast { write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))? } else { write!(f, "{:#}::", self_type.print(cx))? } } else { - if should_show_cast { + if let Some(trait_) = trait_ && should_show_cast { write!(f, "<{} as {}>::", self_type.print(cx), trait_.print(cx))? } else { write!(f, "{}::", self_type.print(cx))? @@ -1138,15 +1142,36 @@ fn fmt_type<'cx>( // the ugliness comes from inlining across crates where // everything comes in as a fully resolved QPath (hard to // look at). - if !f.alternate() && let Ok((url, _, path)) = href(trait_.def_id(), cx) { - write!( - f, - "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \ - title=\"type {path}::{name}\">{name}</a>", - shortty = ItemType::AssocType, - name = assoc.name, - path = join_with_double_colon(&path), - ) + if !f.alternate() { + // FIXME(inherent_associated_types): We always link to the very first associated + // type (in respect to source order) that bears the given name (`assoc.name`) and that is + // affiliated with the computed `DefId`. This is obviously incorrect when we have + // multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself + // through here and map it to the corresponding HTML ID that was generated by + // `render::Context::derive_id` when the impl blocks were rendered. + // There is no such mapping unfortunately. + // As a hack, we could badly imitate `derive_id` here by keeping *count* when looking + // for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()` + // considering privacy, `doc(hidden)`, etc. + // I don't feel like that right now :cold_sweat:. + + let parent_href = match trait_ { + Some(trait_) => href(trait_.def_id(), cx).ok(), + None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()), + }; + + if let Some((url, _, path)) = parent_href { + write!( + f, + "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \ + title=\"type {path}::{name}\">{name}</a>", + shortty = ItemType::AssocType, + name = assoc.name, + path = join_with_double_colon(&path), + ) + } else { + write!(f, "{}", assoc.name) + } } else { write!(f, "{}", assoc.name) }?; diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index b61dd5714..c94968b48 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -514,7 +514,7 @@ struct Classifier<'src> { impl<'src> Classifier<'src> { /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code - /// file span which will be used later on by the `span_correspondance_map`. + /// file span which will be used later on by the `span_correspondence_map`. fn new(src: &str, file_span: Span, decoration_info: Option<DecorationInfo>) -> Classifier<'_> { let tokens = PeekIter::new(TokenIter { src, cursor: Cursor::new(src) }); let decorations = decoration_info.map(Decorations::new); @@ -649,7 +649,7 @@ impl<'src> Classifier<'src> { /// /// `before` is the position of the given token in the `source` string and is used as "lo" byte /// in case we want to try to generate a link for this token using the - /// `span_correspondance_map`. + /// `span_correspondence_map`. fn advance( &mut self, token: TokenKind, @@ -811,7 +811,9 @@ impl<'src> Classifier<'src> { | LiteralKind::Str { .. } | LiteralKind::ByteStr { .. } | LiteralKind::RawStr { .. } - | LiteralKind::RawByteStr { .. } => Class::String, + | LiteralKind::RawByteStr { .. } + | LiteralKind::CStr { .. } + | LiteralKind::RawCStr { .. } => Class::String, // Number literals. LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number, }, @@ -895,7 +897,7 @@ fn exit_span(out: &mut impl Write, closing_tag: &str) { /// flexible. /// /// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function -/// will then try to find this `span` in the `span_correspondance_map`. If found, it'll then +/// will then try to find this `span` in the `span_correspondence_map`. If found, it'll then /// generate a link for this element (which corresponds to where its definition is located). fn string<T: Display>( out: &mut impl Write, @@ -916,7 +918,7 @@ fn string<T: Display>( /// * If `klass` is `Some` but `klass.get_span()` is `None`, it writes the text wrapped in a /// `<span>` with the provided `klass`. /// * If `klass` is `Some` and has a [`rustc_span::Span`], it then tries to generate a link (`<a>` -/// element) by retrieving the link information from the `span_correspondance_map` that was filled +/// element) by retrieving the link information from the `span_correspondence_map` that was filled /// in `span_map.rs::collect_spans_and_sources`. If it cannot retrieve the information, then it's /// the same as the second point (`klass` is `Some` but doesn't have a [`rustc_span::Span`]). fn string_without_closing_tag<T: Display>( @@ -963,7 +965,7 @@ fn string_without_closing_tag<T: Display>( if let Some(href_context) = href_context { if let Some(href) = - href_context.context.shared.span_correspondance_map.get(&def_span).and_then(|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. diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 00aadb8e8..9bb20022c 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -551,7 +551,15 @@ impl<'a, I: Iterator<Item = Event<'a>>> SummaryLine<'a, I> { } fn check_if_allowed_tag(t: &Tag<'_>) -> bool { - matches!(t, Tag::Paragraph | Tag::Emphasis | Tag::Strong | Tag::Link(..) | Tag::BlockQuote) + matches!( + t, + Tag::Paragraph + | Tag::Emphasis + | Tag::Strong + | Tag::Strikethrough + | Tag::Link(..) + | Tag::BlockQuote + ) } fn is_forbidden_tag(t: &Tag<'_>) -> bool { @@ -773,7 +781,7 @@ impl<'tcx> ExtraInfo<'tcx> { ExtraInfo { def_id, sp, tcx } } - fn error_invalid_codeblock_attr(&self, msg: &str, help: &str) { + fn error_invalid_codeblock_attr(&self, msg: String, help: &str) { if let Some(def_id) = self.def_id.as_local() { self.tcx.struct_span_lint_hir( crate::lint::INVALID_CODEBLOCK_ATTRIBUTES, @@ -948,7 +956,7 @@ impl LangString { } { if let Some(extra) = extra { extra.error_invalid_codeblock_attr( - &format!("unknown attribute `{}`. Did you mean `{}`?", x, flag), + format!("unknown attribute `{}`. Did you mean `{}`?", x, flag), help, ); } @@ -1229,7 +1237,27 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin pub(crate) struct MarkdownLink { pub kind: LinkType, pub link: String, - pub range: Range<usize>, + pub range: MarkdownLinkRange, +} + +#[derive(Clone, Debug)] +pub(crate) enum MarkdownLinkRange { + /// Normally, markdown link warnings point only at the destination. + Destination(Range<usize>), + /// In some cases, it's not possible to point at the destination. + /// Usually, this happens because backslashes `\\` are used. + /// When that happens, point at the whole link, and don't provide structured suggestions. + WholeLink(Range<usize>), +} + +impl MarkdownLinkRange { + /// Extracts the inner range. + pub fn inner_range(&self) -> &Range<usize> { + match self { + MarkdownLinkRange::Destination(range) => range, + MarkdownLinkRange::WholeLink(range) => range, + } + } } pub(crate) fn markdown_links<R>( @@ -1249,9 +1277,9 @@ pub(crate) fn markdown_links<R>( if md_start <= s_start && s_end <= md_end { let start = s_start.offset_from(md_start) as usize; let end = s_end.offset_from(md_start) as usize; - start..end + MarkdownLinkRange::Destination(start..end) } else { - fallback + MarkdownLinkRange::WholeLink(fallback) } }; @@ -1259,6 +1287,7 @@ pub(crate) fn markdown_links<R>( // For diagnostics, we want to underline the link's definition but `span` will point at // where the link is used. This is a problem for reference-style links, where the definition // is separate from the usage. + match link { // `Borrowed` variant means the string (the link's destination) may come directly from // the markdown text and we can locate the original link destination. @@ -1267,10 +1296,82 @@ pub(crate) fn markdown_links<R>( CowStr::Borrowed(s) => locate(s, span), // For anything else, we can only use the provided range. - CowStr::Boxed(_) | CowStr::Inlined(_) => span, + CowStr::Boxed(_) | CowStr::Inlined(_) => MarkdownLinkRange::WholeLink(span), } }; + let span_for_offset_backward = |span: Range<usize>, open: u8, close: u8| { + let mut open_brace = !0; + let mut close_brace = !0; + for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate().rev() { + let i = i + span.start; + if b == close { + close_brace = i; + break; + } + } + if close_brace < span.start || close_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + let mut nesting = 1; + for (i, b) in md.as_bytes()[span.start..close_brace].iter().copied().enumerate().rev() { + let i = i + span.start; + if b == close { + nesting += 1; + } + if b == open { + nesting -= 1; + } + if nesting == 0 { + open_brace = i; + break; + } + } + assert!(open_brace != close_brace); + if open_brace < span.start || open_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + // do not actually include braces in the span + let range = (open_brace + 1)..close_brace; + MarkdownLinkRange::Destination(range.clone()) + }; + + let span_for_offset_forward = |span: Range<usize>, open: u8, close: u8| { + let mut open_brace = !0; + let mut close_brace = !0; + for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate() { + let i = i + span.start; + if b == open { + open_brace = i; + break; + } + } + if open_brace < span.start || open_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + let mut nesting = 0; + for (i, b) in md.as_bytes()[open_brace..span.end].iter().copied().enumerate() { + let i = i + open_brace; + if b == close { + nesting -= 1; + } + if b == open { + nesting += 1; + } + if nesting == 0 { + close_brace = i; + break; + } + } + assert!(open_brace != close_brace); + if open_brace < span.start || open_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + // do not actually include braces in the span + let range = (open_brace + 1)..close_brace; + MarkdownLinkRange::Destination(range.clone()) + }; + Parser::new_with_broken_link_callback( md, main_body_opts(), @@ -1279,11 +1380,20 @@ pub(crate) fn markdown_links<R>( .into_offset_iter() .filter_map(|(event, span)| match event { Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => { - preprocess_link(MarkdownLink { - kind: link_type, - range: span_for_link(&dest, span), - link: dest.into_string(), - }) + let range = match link_type { + // Link is pulled from the link itself. + LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => { + span_for_offset_backward(span, b'[', b']') + } + LinkType::CollapsedUnknown => span_for_offset_forward(span, b'[', b']'), + LinkType::Inline => span_for_offset_backward(span, b'(', b')'), + // Link is pulled from elsewhere in the document. + LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => { + span_for_link(&dest, span) + } + LinkType::Autolink | LinkType::Email => unreachable!(), + }; + preprocess_link(MarkdownLink { kind: link_type, range, link: dest.into_string() }) } _ => None, }) @@ -1392,7 +1502,7 @@ static DEFAULT_ID_MAP: Lazy<FxHashMap<Cow<'static, str>, usize>> = Lazy::new(|| fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> { let mut map = FxHashMap::default(); - // This is the list of IDs used in Javascript. + // This is the list of IDs used in JavaScript. map.insert("help".into(), 1); map.insert("settings".into(), 1); map.insert("not-displayed".into(), 1); diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index ac5054ce1..56af257fd 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -122,9 +122,9 @@ pub(crate) struct SharedContext<'tcx> { /// the crate. redirections: Option<RefCell<FxHashMap<String, String>>>, - /// Correspondance map used to link types used in the source code pages to allow to click on + /// 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_correspondance_map: FxHashMap<rustc_span::Span, LinkFromSrc>, + pub(crate) span_correspondence_map: FxHashMap<rustc_span::Span, LinkFromSrc>, /// The [`Cache`] used during rendering. pub(crate) cache: Cache, @@ -184,11 +184,8 @@ impl<'tcx> Context<'tcx> { }; title.push_str(" - Rust"); let tyname = it.type_(); - let desc = it - .doc_value() - .as_ref() - .map(|doc| plain_text_summary(doc, &it.link_names(&self.cache()))); - let desc = if let Some(desc) = desc { + let desc = plain_text_summary(&it.doc_value(), &it.link_names(&self.cache())); + let desc = if !desc.is_empty() { desc } else if it.is_crate() { format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) @@ -531,7 +528,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { errors: receiver, redirections: if generate_redirect_map { Some(Default::default()) } else { None }, show_type_layout, - span_correspondance_map: matches, + span_correspondence_map: matches, cache, call_locations, }; @@ -647,7 +644,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { </div>\ <noscript>\ <section>\ - You need to enable Javascript be able to update your settings.\ + You need to enable JavaScript be able to update your settings.\ </section>\ </noscript>\ <link rel=\"stylesheet\" \ @@ -709,7 +706,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { </div>\ <noscript>\ <section>\ - <p>You need to enable Javascript to use keyboard commands or search.</p>\ + <p>You need to enable JavaScript to use keyboard commands or search.</p>\ <p>For more information, browse the <a href=\"https://doc.rust-lang.org/rustdoc/\">rustdoc handbook</a>.</p>\ </section>\ </noscript>", @@ -746,7 +743,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { // Flush pending errors. Rc::get_mut(&mut self.shared).unwrap().fs.close(); let nb_errors = - self.shared.errors.iter().map(|err| self.tcx().sess.struct_err(&err).emit()).count(); + self.shared.errors.iter().map(|err| self.tcx().sess.struct_err(err).emit()).count(); if nb_errors > 0 { Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), "")) } else { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 463184aca..9e3b5d10a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -32,6 +32,7 @@ mod context; mod print_item; mod sidebar; mod span_map; +mod type_layout; mod write_shared; pub(crate) use self::context::*; @@ -47,7 +48,6 @@ use std::str; use std::string::ToString; use askama::Template; -use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -468,7 +468,8 @@ fn document_short<'a, 'cx: 'a>( if !show_def_docs { return Ok(()); } - if let Some(s) = item.doc_value() { + let s = item.doc_value(); + if !s.is_empty() { let (mut summary_html, has_more_content) = MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content(); @@ -511,7 +512,7 @@ fn document_full_inner<'a, 'cx: 'a>( heading_offset: HeadingOffset, ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { - if let Some(s) = item.collapsed_doc_value() { + if let Some(s) = item.opt_doc_value() { debug!("Doc block: =====\n{}\n=====", s); if is_collapsible { write!( @@ -848,10 +849,10 @@ fn assoc_method( let (indent, indent_str, end_newline) = if parent == ItemType::Trait { header_len += 4; let indent_str = " "; - write!(w, "{}", render_attributes_in_pre(meth, indent_str)); + write!(w, "{}", render_attributes_in_pre(meth, indent_str, tcx)); (4, indent_str, Ending::NoNewline) } else { - render_attributes_in_code(w, meth); + render_attributes_in_code(w, meth, tcx); (0, "", Ending::Newline) }; w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len()); @@ -1020,36 +1021,15 @@ fn render_assoc_item( } } -const ALLOWED_ATTRIBUTES: &[Symbol] = - &[sym::export_name, sym::link_section, sym::no_mangle, sym::repr, sym::non_exhaustive]; - -fn attributes(it: &clean::Item) -> Vec<String> { - it.attrs - .other_attrs - .iter() - .filter_map(|attr| { - if ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) { - Some( - pprust::attribute_to_string(attr) - .replace("\\\n", "") - .replace('\n', "") - .replace(" ", " "), - ) - } else { - None - } - }) - .collect() -} - // When an attribute is rendered inside a `<pre>` tag, it is formatted using // a whitespace prefix and newline. -fn render_attributes_in_pre<'a>( +fn render_attributes_in_pre<'a, 'b: 'a>( it: &'a clean::Item, prefix: &'a str, -) -> impl fmt::Display + Captures<'a> { + tcx: TyCtxt<'b>, +) -> impl fmt::Display + Captures<'a> + Captures<'b> { crate::html::format::display_fn(move |f| { - for a in attributes(it) { + for a in it.attributes(tcx, false) { writeln!(f, "{}{}", prefix, a)?; } Ok(()) @@ -1058,8 +1038,8 @@ fn render_attributes_in_pre<'a>( // 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 Buffer, it: &clean::Item) { - for a in attributes(it) { +fn render_attributes_in_code(w: &mut Buffer, it: &clean::Item, tcx: TyCtxt<'_>) { + for a in it.attributes(tcx, false) { write!(w, "<div class=\"code-attribute\">{}</div>", a); } } @@ -1154,10 +1134,10 @@ fn render_assoc_items_inner( let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none()); if !non_trait.is_empty() { let mut tmp_buf = Buffer::html(); - let (render_mode, id) = match what { + let (render_mode, id, class_html) = match what { AssocItemRender::All => { write_impl_section_heading(&mut tmp_buf, "Implementations", "implementations"); - (RenderMode::Normal, "implementations-list".to_owned()) + (RenderMode::Normal, "implementations-list".to_owned(), "") } AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => { let id = @@ -1174,7 +1154,11 @@ fn render_assoc_items_inner( ), &id, ); - (RenderMode::ForDeref { mut_: deref_mut_ }, cx.derive_id(id)) + ( + RenderMode::ForDeref { mut_: deref_mut_ }, + cx.derive_id(id), + r#" class="impl-items""#, + ) } }; let mut impls_buf = Buffer::html(); @@ -1198,7 +1182,7 @@ fn render_assoc_items_inner( } if !impls_buf.is_empty() { write!(w, "{}", tmp_buf.into_inner()).unwrap(); - write!(w, "<div id=\"{}\">", id).unwrap(); + write!(w, "<div id=\"{id}\"{class_html}>").unwrap(); write!(w, "{}", impls_buf.into_inner()).unwrap(); w.write_str("</div>").unwrap(); } @@ -1493,7 +1477,7 @@ fn render_impl( if let Some(it) = t.items.iter().find(|i| i.name == item.name) { // We need the stability of the item from the trait // because impls can't have a stability. - if item.doc_value().is_some() { + if !item.doc_value().is_empty() { document_item_info(cx, it, Some(parent)) .render_into(&mut info_buffer) .unwrap(); @@ -1764,11 +1748,11 @@ fn render_impl( write!(w, "</summary>") } - if let Some(ref dox) = i.impl_item.collapsed_doc_value() { + if let Some(ref dox) = i.impl_item.opt_doc_value() { if trait_.is_none() && i.inner_impl().items.is_empty() { w.write_str( "<div class=\"item-info\">\ - <div class=\"stab empty-impl\">This impl block contains no items.</div> + <div class=\"stab empty-impl\">This impl block contains no items.</div>\ </div>", ); } @@ -1787,12 +1771,14 @@ fn render_impl( .into_string() ); } + if !default_impl_items.is_empty() || !impl_items.is_empty() { + w.write_str("<div class=\"impl-items\">"); + close_tags.insert_str(0, "</div>"); + } } if !default_impl_items.is_empty() || !impl_items.is_empty() { - w.write_str("<div class=\"impl-items\">"); w.push_buffer(default_impl_items); w.push_buffer(impl_items); - close_tags.insert_str(0, "</div>"); } w.write_str(&close_tags); } @@ -1947,8 +1933,6 @@ pub(crate) fn small_url_encode(s: String) -> String { // While the same is not true for hashes, rustdoc only needs to be // consistent with itself when encoding them. st += "+"; - } else if b == b'%' { - st += "%%"; } else { write!(st, "%{:02X}", b).unwrap(); } @@ -2217,7 +2201,9 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> { } clean::Type::QPath(box clean::QPathData { self_type, trait_, .. }) => { work.push_back(self_type); - process_path(trait_.def_id()); + if let Some(trait_) = trait_ { + process_path(trait_.def_id()); + } } _ => {} } @@ -2271,8 +2257,7 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &c Ok(contents) => contents, Err(err) => { let span = item.span(tcx).map_or(rustc_span::DUMMY_SP, |span| span.inner()); - tcx.sess - .span_err(span, &format!("failed to read file {}: {}", path.display(), err)); + tcx.sess.span_err(span, format!("failed to read file {}: {}", path.display(), err)); return false; } }; diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 9a968e48b..62027a3fa 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -6,16 +6,16 @@ use rustc_hir as hir; use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; use rustc_middle::middle::stability; -use rustc_middle::span_bug; -use rustc_middle::ty::layout::LayoutError; -use rustc_middle::ty::{self, Adt, TyCtxt}; +use rustc_middle::ty::{self, TyCtxt}; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Symbol}; -use rustc_target::abi::{LayoutS, Primitive, TagEncoding, Variants}; +use std::borrow::Borrow; +use std::cell::{RefCell, RefMut}; use std::cmp::Ordering; use std::fmt; use std::rc::Rc; +use super::type_layout::document_type_layout; 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, @@ -218,6 +218,53 @@ fn toggle_close(mut w: impl fmt::Write) { w.write_str("</details>").unwrap(); } +trait ItemTemplate<'a, 'cx: 'a>: askama::Template + fmt::Display { + fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>); +} + +fn item_template_document<'a: 'b, 'b, 'cx: 'a>( + templ: &'b impl ItemTemplate<'a, 'cx>, +) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let (item, mut cx) = templ.item_and_mut_cx(); + let v = document(*cx, item, None, HeadingOffset::H2); + write!(f, "{v}") + }) +} + +fn item_template_document_type_layout<'a: 'b, 'b, 'cx: 'a>( + templ: &'b impl ItemTemplate<'a, 'cx>, +) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let (item, cx) = templ.item_and_mut_cx(); + let def_id = item.item_id.expect_def_id(); + let v = document_type_layout(*cx, def_id); + write!(f, "{v}") + }) +} + +fn item_template_render_attributes_in_pre<'a: 'b, 'b, 'cx: 'a>( + templ: &'b impl ItemTemplate<'a, 'cx>, +) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let (item, cx) = templ.item_and_mut_cx(); + let tcx = cx.tcx(); + let v = render_attributes_in_pre(item, "", tcx); + write!(f, "{v}") + }) +} + +fn item_template_render_assoc_items<'a: 'b, 'b, 'cx: 'a>( + templ: &'b impl ItemTemplate<'a, 'cx>, +) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let (item, mut cx) = templ.item_and_mut_cx(); + let def_id = item.item_id.expect_def_id(); + let v = render_assoc_items(*cx, item, def_id, AssocItemRender::All); + write!(f, "{v}") + }) +} + fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) { write!(w, "{}", document(cx, item, None, HeadingOffset::H2)); @@ -358,18 +405,18 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: clean::ImportItem(ref import) => { let stab_tags = if let Some(import_def_id) = import.source.did { - let ast_attrs = cx.tcx().get_attrs_unchecked(import_def_id); + let ast_attrs = tcx.get_attrs_unchecked(import_def_id); let import_attrs = Box::new(clean::Attributes::from_ast(ast_attrs)); // Just need an item with the correct def_id and attrs let import_item = clean::Item { item_id: import_def_id.into(), attrs: import_attrs, - cfg: ast_attrs.cfg(cx.tcx(), &cx.cache().hidden_cfg), + cfg: ast_attrs.cfg(tcx, &cx.cache().hidden_cfg), ..myitem.clone() }; - let stab_tags = Some(extra_info_tags(&import_item, item, cx.tcx()).to_string()); + let stab_tags = Some(extra_info_tags(&import_item, item, tcx).to_string()); stab_tags } else { None @@ -407,8 +454,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: let unsafety_flag = match *myitem.kind { clean::FunctionItem(_) | clean::ForeignFunctionItem(_) - if myitem.fn_header(cx.tcx()).unwrap().unsafety - == hir::Unsafety::Unsafe => + if myitem.fn_header(tcx).unwrap().unsafety == hir::Unsafety::Unsafe => { "<sup title=\"unsafe function\">⚠</sup>" } @@ -422,9 +468,9 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: _ => "", }; - let doc_value = myitem.doc_value().unwrap_or_default(); w.write_str(ITEM_TABLE_ROW_OPEN); - let docs = MarkdownSummaryLine(&doc_value, &myitem.links(cx)).into_string(); + let docs = + MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)).into_string(); let (docs_before, docs_after) = if docs.is_empty() { ("", "") } else { @@ -441,7 +487,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: {docs_before}{docs}{docs_after}", name = myitem.name.unwrap(), visibility_emoji = visibility_emoji, - stab_tags = extra_info_tags(myitem, item, cx.tcx()), + stab_tags = extra_info_tags(myitem, item, tcx), class = myitem.type_(), unsafety_flag = unsafety_flag, href = item_path(myitem.type_(), myitem.name.unwrap().as_str()), @@ -550,7 +596,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle w, "{attrs}{vis}{constness}{asyncness}{unsafety}{abi}fn \ {name}{generics}{decl}{notable_traits}{where_clause}", - attrs = render_attributes_in_pre(it, ""), + attrs = render_attributes_in_pre(it, "", tcx), vis = visibility, constness = constness, asyncness = asyncness, @@ -591,7 +637,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: it.name.unwrap(), t.generics.print(cx), bounds, - attrs = render_attributes_in_pre(it, ""), + attrs = render_attributes_in_pre(it, "", tcx), ); if !t.generics.where_predicates.is_empty() { @@ -888,7 +934,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", ""); for implementor in foreign { - let provided_methods = implementor.inner_impl().provided_trait_methods(cx.tcx()); + let provided_methods = implementor.inner_impl().provided_trait_methods(tcx); let assoc_link = AssocItemLink::GotoSource(implementor.impl_item.item_id, &provided_methods); render_impl( @@ -921,7 +967,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } w.write_str("</div>"); - if t.is_auto(cx.tcx()) { + if t.is_auto(tcx) { write_small_section_header( w, "synthetic-implementors", @@ -950,7 +996,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: "<div id=\"implementors-list\"></div>", ); - if t.is_auto(cx.tcx()) { + if t.is_auto(tcx) { write_small_section_header( w, "synthetic-implementors", @@ -1065,7 +1111,7 @@ fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: & t.generics.print(cx), print_where_clause(&t.generics, cx, 0, Ending::Newline), bounds(&t.bounds, true, cx), - attrs = render_attributes_in_pre(it, ""), + attrs = render_attributes_in_pre(it, "", cx.tcx()), ); }); @@ -1087,7 +1133,7 @@ fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &cl t.generics.print(cx), where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), bounds = bounds(&t.bounds, false, cx), - attrs = render_attributes_in_pre(it, ""), + attrs = render_attributes_in_pre(it, "", cx.tcx()), ); }); @@ -1111,7 +1157,7 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea t.generics.print(cx), where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), type_ = t.type_.print(cx), - attrs = render_attributes_in_pre(it, ""), + attrs = render_attributes_in_pre(it, "", cx.tcx()), ); }); } @@ -1133,32 +1179,18 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean: #[derive(Template)] #[template(path = "item_union.html")] struct ItemUnion<'a, 'cx> { - cx: std::cell::RefCell<&'a mut Context<'cx>>, + cx: RefCell<&'a mut Context<'cx>>, it: &'a clean::Item, s: &'a clean::Union, } - impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { - fn render_assoc_items<'b>( - &'b self, - ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let def_id = self.it.item_id.expect_def_id(); - let mut cx = self.cx.borrow_mut(); - let v = render_assoc_items(*cx, self.it, def_id, AssocItemRender::All); - write!(f, "{v}") - }) - } - fn document_type_layout<'b>( - &'b self, - ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let def_id = self.it.item_id.expect_def_id(); - let cx = self.cx.borrow_mut(); - let v = document_type_layout(*cx, def_id); - write!(f, "{v}") - }) + impl<'a, 'cx: 'a> ItemTemplate<'a, 'cx> for ItemUnion<'a, 'cx> { + fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>) { + (self.it, self.cx.borrow_mut()) } + } + + impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { display_fn(move |f| { let cx = self.cx.borrow_mut(); @@ -1166,21 +1198,6 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean: write!(f, "{v}") }) } - fn render_attributes_in_pre<'b>( - &'b self, - ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let v = render_attributes_in_pre(self.it, ""); - write!(f, "{v}") - }) - } - fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let mut cx = self.cx.borrow_mut(); - let v = document(*cx, self.it, None, HeadingOffset::H2); - write!(f, "{v}") - }) - } fn document_field<'b>( &'b self, field: &'a clean::Item, @@ -1220,7 +1237,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean: } } - ItemUnion { cx: std::cell::RefCell::new(cx), it, s }.render_into(w).unwrap(); + ItemUnion { cx: RefCell::new(cx), it, s }.render_into(w).unwrap(); } fn print_tuple_struct_fields<'a, 'cx: 'a>( @@ -1246,13 +1263,13 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: let tcx = cx.tcx(); let count_variants = e.variants().count(); wrap_item(w, |mut w| { + render_attributes_in_code(w, it, tcx); write!( w, - "{attrs}{}enum {}{}", + "{}enum {}{}", visibility_print_with_space(it.visibility(tcx), it.item_id, cx), it.name.unwrap(), e.generics.print(cx), - attrs = render_attributes_in_pre(it, ""), ); if !print_where_clause_and_check(w, &e.generics, cx) { // If there wasn't a `where` clause, we add a whitespace. @@ -1339,7 +1356,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: clean::VariantKind::Tuple(fields) => { // Documentation on tuple variant fields is rare, so to reduce noise we only emit // the section if at least one field is documented. - if fields.iter().any(|f| f.doc_value().is_some()) { + if fields.iter().any(|f| !f.doc_value().is_empty()) { Some(("Tuple Fields", fields)) } else { None @@ -1447,7 +1464,7 @@ fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &clean::Constant) { wrap_item(w, |w| { let tcx = cx.tcx(); - render_attributes_in_code(w, it); + render_attributes_in_code(w, it, tcx); write!( w, @@ -1494,7 +1511,7 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) { wrap_item(w, |w| { - render_attributes_in_code(w, it); + render_attributes_in_code(w, it, cx.tcx()); render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx); }); @@ -1542,11 +1559,12 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean write!(w, "{}", document_type_layout(cx, def_id)); } -fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) { - wrap_item(w, |w| { - render_attributes_in_code(w, it); +fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) { + let mut buffer = Buffer::new(); + wrap_item(&mut buffer, |buffer| { + render_attributes_in_code(buffer, it, cx.tcx()); write!( - w, + buffer, "{vis}static {mutability}{name}: {typ}", vis = visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), mutability = s.mutability.print_with_space(), @@ -1554,13 +1572,16 @@ fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean typ = s.type_.print(cx) ); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + + write!(w, "{}", buffer.into_inner()).unwrap(); + + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); } fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { wrap_item(w, |w| { w.write_str("extern {\n"); - render_attributes_in_code(w, it); + render_attributes_in_code(w, it, cx.tcx()); write!( w, " {}type {};\n}}", @@ -1933,118 +1954,6 @@ fn document_non_exhaustive<'a>(item: &'a clean::Item) -> impl fmt::Display + 'a }) } -fn document_type_layout<'a, 'cx: 'a>( - cx: &'a Context<'cx>, - ty_def_id: DefId, -) -> impl fmt::Display + 'a + Captures<'cx> { - fn write_size_of_layout(mut w: impl fmt::Write, layout: &LayoutS, tag_size: u64) { - if layout.abi.is_unsized() { - write!(w, "(unsized)").unwrap(); - } else { - let size = layout.size.bytes() - tag_size; - write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" }).unwrap(); - if layout.abi.is_uninhabited() { - write!( - w, - " (<a href=\"https://doc.rust-lang.org/stable/reference/glossary.html#uninhabited\">uninhabited</a>)" - ).unwrap(); - } - } - } - - display_fn(move |mut f| { - if !cx.shared.show_type_layout { - return Ok(()); - } - - writeln!( - f, - "<h2 id=\"layout\" class=\"small-section-header\"> \ - Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>" - )?; - writeln!(f, "<div class=\"docblock\">")?; - - let tcx = cx.tcx(); - let param_env = tcx.param_env(ty_def_id); - let ty = tcx.type_of(ty_def_id).subst_identity(); - match tcx.layout_of(param_env.and(ty)) { - Ok(ty_layout) => { - writeln!( - f, - "<div class=\"warning\"><p><strong>Note:</strong> Most layout information is \ - <strong>completely unstable</strong> and may even differ between compilations. \ - The only exception is types with certain <code>repr(...)</code> attributes. \ - Please see the Rust Reference’s \ - <a href=\"https://doc.rust-lang.org/reference/type-layout.html\">“Type Layout”</a> \ - chapter for details on type layout guarantees.</p></div>" - )?; - f.write_str("<p><strong>Size:</strong> ")?; - write_size_of_layout(&mut f, &ty_layout.layout.0, 0); - writeln!(f, "</p>")?; - if let Variants::Multiple { variants, tag, tag_encoding, .. } = - &ty_layout.layout.variants() - { - if !variants.is_empty() { - f.write_str( - "<p><strong>Size for each variant:</strong></p>\ - <ul>", - )?; - - let Adt(adt, _) = ty_layout.ty.kind() else { - span_bug!(tcx.def_span(ty_def_id), "not an adt") - }; - - let tag_size = if let TagEncoding::Niche { .. } = tag_encoding { - 0 - } else if let Primitive::Int(i, _) = tag.primitive() { - i.size().bytes() - } else { - span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int") - }; - - for (index, layout) in variants.iter_enumerated() { - let name = adt.variant(index).name; - write!(&mut f, "<li><code>{name}</code>: ")?; - write_size_of_layout(&mut f, layout, tag_size); - writeln!(&mut f, "</li>")?; - } - f.write_str("</ul>")?; - } - } - } - // This kind of layout error can occur with valid code, e.g. if you try to - // get the layout of a generic type such as `Vec<T>`. - Err(LayoutError::Unknown(_)) => { - writeln!( - f, - "<p><strong>Note:</strong> Unable to compute type layout, \ - possibly due to this type having generic parameters. \ - Layout can only be computed for concrete, fully-instantiated types.</p>" - )?; - } - // This kind of error probably can't happen with valid code, but we don't - // want to panic and prevent the docs from building, so we just let the - // user know that we couldn't compute the layout. - Err(LayoutError::SizeOverflow(_)) => { - writeln!( - f, - "<p><strong>Note:</strong> Encountered an error during type layout; \ - the type was too big.</p>" - )?; - } - Err(LayoutError::NormalizationFailure(_, _)) => { - writeln!( - f, - "<p><strong>Note:</strong> Encountered an error during type layout; \ - the type failed to be normalized.</p>" - )?; - } - } - - writeln!(f, "</div>") - }) -} - fn pluralize(count: usize) -> &'static str { if count > 1 { "s" } else { "" } } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index f5b4a3f5a..846299f02 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -28,9 +28,7 @@ pub(crate) fn build_index<'tcx>( // has since been learned. for &OrphanImplItem { parent, ref item, ref impl_generics } in &cache.orphan_impl_items { if let Some((fqp, _)) = cache.paths.get(&parent) { - let desc = item - .doc_value() - .map_or_else(String::new, |s| short_markdown_summary(&s, &item.link_names(cache))); + let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache)); cache.search_index.push(IndexItem { ty: item.type_(), name: item.name.unwrap(), @@ -45,10 +43,8 @@ pub(crate) fn build_index<'tcx>( } } - let crate_doc = krate - .module - .doc_value() - .map_or_else(String::new, |s| short_markdown_summary(&s, &krate.module.link_names(cache))); + let crate_doc = + short_markdown_summary(&krate.module.doc_value(), &krate.module.link_names(cache)); // Aliases added through `#[doc(alias = "...")]`. Since a few items can have the same alias, // we need the alias element to have an array of items. @@ -391,12 +387,14 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> { clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => { get_index_type_id(type_) } + // The type parameters are converted to generics in `add_generics_and_bounds_as_types` + clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)), + clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)), + // Not supported yet clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) | clean::Tuple(_) - | clean::Slice(_) - | clean::Array(_, _) | clean::QPath { .. } | clean::Infer => None, } @@ -563,6 +561,30 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>( } } insert_ty(res, arg.clone(), ty_generics); + } else if let Type::Slice(ref ty) = *arg { + let mut ty_generics = Vec::new(); + add_generics_and_bounds_as_types( + self_, + generics, + &ty, + tcx, + recurse + 1, + &mut ty_generics, + cache, + ); + insert_ty(res, arg.clone(), ty_generics); + } else if let Type::Array(ref ty, _) = *arg { + let mut ty_generics = Vec::new(); + add_generics_and_bounds_as_types( + self_, + generics, + &ty, + tcx, + recurse + 1, + &mut ty_generics, + cache, + ); + insert_ty(res, arg.clone(), ty_generics); } else { // This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're // looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't. diff --git a/src/librustdoc/html/render/type_layout.rs b/src/librustdoc/html/render/type_layout.rs new file mode 100644 index 000000000..c9b95b1e6 --- /dev/null +++ b/src/librustdoc/html/render/type_layout.rs @@ -0,0 +1,86 @@ +use askama::Template; + +use rustc_data_structures::captures::Captures; +use rustc_hir::def_id::DefId; +use rustc_middle::span_bug; +use rustc_middle::ty::layout::LayoutError; +use rustc_middle::ty::Adt; +use rustc_span::symbol::Symbol; +use rustc_target::abi::{Primitive, TagEncoding, Variants}; + +use std::fmt; + +use crate::html::format::display_fn; +use crate::html::render::Context; + +#[derive(Template)] +#[template(path = "type_layout.html")] +struct TypeLayout<'cx> { + variants: Vec<(Symbol, TypeLayoutSize)>, + type_layout_size: Result<TypeLayoutSize, LayoutError<'cx>>, +} + +#[derive(Template)] +#[template(path = "type_layout_size.html")] +struct TypeLayoutSize { + is_unsized: bool, + is_uninhabited: bool, + size: u64, +} + +pub(crate) fn document_type_layout<'a, 'cx: 'a>( + cx: &'a Context<'cx>, + ty_def_id: DefId, +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |f| { + if !cx.shared.show_type_layout { + return Ok(()); + } + + let tcx = cx.tcx(); + let param_env = tcx.param_env(ty_def_id); + let ty = tcx.type_of(ty_def_id).subst_identity(); + let type_layout = tcx.layout_of(param_env.and(ty)); + + let variants = + if let Ok(type_layout) = type_layout && + let Variants::Multiple { variants, tag, tag_encoding, .. } = + type_layout.layout.variants() && + !variants.is_empty() + { + let tag_size = + if let TagEncoding::Niche { .. } = tag_encoding { + 0 + } else if let Primitive::Int(i, _) = tag.primitive() { + i.size().bytes() + } else { + span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int") + }; + variants + .iter_enumerated() + .map(|(variant_idx, variant_layout)| { + let Adt(adt, _) = type_layout.ty.kind() else { + span_bug!(tcx.def_span(ty_def_id), "not an adt") + }; + let name = adt.variant(variant_idx).name; + let is_unsized = variant_layout.abi.is_unsized(); + let is_uninhabited = variant_layout.abi.is_uninhabited(); + let size = variant_layout.size.bytes() - tag_size; + let type_layout_size = TypeLayoutSize { is_unsized, is_uninhabited, size }; + (name, type_layout_size) + }) + .collect() + } else { + Vec::new() + }; + + let type_layout_size = tcx.layout_of(param_env.and(ty)).map(|layout| { + let is_unsized = layout.abi.is_unsized(); + let is_uninhabited = layout.abi.is_uninhabited(); + let size = layout.size.bytes(); + TypeLayoutSize { is_unsized, is_uninhabited, size } + }); + + Ok(TypeLayout { variants, type_layout_size }.render_into(f).unwrap()) + }) +} diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index c8397967c..a26fa3749 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -145,7 +145,7 @@ impl DocVisitor for SourceCollector<'_, '_> { Err(e) => { self.cx.shared.tcx.sess.span_err( span, - &format!( + format!( "failed to render source code for `{}`: {}", filename.prefer_local(), e, diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 6fbb45086..a7d5f4977 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1259,6 +1259,10 @@ a.tooltip:hover::after { background-color: var(--search-error-code-background-color); } +.search-corrections { + font-weight: normal; +} + #src-sidebar-toggle { position: sticky; top: 0; diff --git a/src/librustdoc/html/static/css/settings.css b/src/librustdoc/html/static/css/settings.css index d13c783d2..c1324c076 100644 --- a/src/librustdoc/html/static/css/settings.css +++ b/src/librustdoc/html/static/css/settings.css @@ -1,13 +1,11 @@ .setting-line { margin: 1.2em 0.6em; - position: relative; } .setting-radio input, .setting-check input { margin-right: 0.3em; height: 1.2rem; width: 1.2rem; - color: inherit; border: 2px solid var(--settings-input-border-color); outline: none; -webkit-appearance: none; @@ -16,11 +14,6 @@ .setting-radio input { border-radius: 50%; } -.setting-check input:checked { - content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">\ - <path d="M7,25L17,32L33,12" fill="none" stroke="black" stroke-width="5"/>\ - <path d="M7,23L17,30L33,10" fill="none" stroke="white" stroke-width="5"/></svg>'); -} .setting-radio span, .setting-check span { padding-bottom: 1px; @@ -53,6 +46,9 @@ .setting-check input:checked { background-color: var(--settings-input-color); border-width: 1px; + content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">\ + <path d="M7,25L17,32L33,12" fill="none" stroke="black" stroke-width="5"/>\ + <path d="M7,23L17,30L33,10" fill="none" stroke="white" stroke-width="5"/></svg>'); } .setting-radio input:focus, .setting-check input:focus { box-shadow: 0 0 1px 1px var(--settings-input-color); diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index 4c81a0979..8b931f74e 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -9,6 +9,7 @@ function initSearch(searchIndex){} /** * @typedef {{ * name: string, + * id: integer, * fullPath: Array<string>, * pathWithoutLast: Array<string>, * pathLast: string, @@ -36,6 +37,8 @@ let ParserState; * args: Array<QueryElement>, * returned: Array<QueryElement>, * foundElems: number, + * literalSearch: boolean, + * corrections: Array<{from: string, to: integer}>, * }} */ let ParsedQuery; @@ -139,7 +142,7 @@ let FunctionSearchType; /** * @typedef {{ - * name: (null|string), + * id: (null|number), * ty: (null|number), * generics: Array<FunctionType>, * }} diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 6f5987e68..bccf675c1 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -275,8 +275,7 @@ function preLoadCss(cssUrl) { document.title = searchState.titleBeforeSearch; // We also remove the query parameter from the URL. if (browserSupportsHistoryApi()) { - history.replaceState(null, window.currentCrate + " - Rust", - getNakedUrl() + window.location.hash); + history.replaceState(null, "", getNakedUrl() + window.location.hash); } }, getQueryStringParams: () => { @@ -376,11 +375,7 @@ function preLoadCss(cssUrl) { function handleEscape(ev) { searchState.clearInputTimeout(); - switchDisplayedElement(null); - if (browserSupportsHistoryApi()) { - history.replaceState(null, window.currentCrate + " - Rust", - getNakedUrl() + window.location.hash); - } + searchState.hideResults(); ev.preventDefault(); searchState.defocus(); window.hideAllModals(true); // true = reset focus for tooltips @@ -535,9 +530,11 @@ function preLoadCss(cssUrl) { // ignored are included in the attribute `data-ignore-extern-crates`. const script = document .querySelector("script[data-ignore-extern-crates]"); - const ignoreExternCrates = script ? script.getAttribute("data-ignore-extern-crates") : ""; + const ignoreExternCrates = new Set( + (script ? script.getAttribute("data-ignore-extern-crates") : "").split(",") + ); for (const lib of libs) { - if (lib === window.currentCrate || ignoreExternCrates.indexOf(lib) !== -1) { + if (lib === window.currentCrate || ignoreExternCrates.has(lib)) { continue; } const structs = imp[lib]; diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 929dae81c..62afe40bb 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -58,6 +58,7 @@ function printTab(nb) { } iter += 1; }); + const isTypeSearch = (nb > 0 || iter === 1); iter = 0; onEachLazy(document.getElementById("results").childNodes, elem => { if (nb === iter) { @@ -70,6 +71,13 @@ function printTab(nb) { }); if (foundCurrentTab && foundCurrentResultSet) { searchState.currentTab = nb; + // Corrections only kick in on type-based searches. + const correctionsElem = document.getElementsByClassName("search-corrections"); + if (isTypeSearch) { + removeClass(correctionsElem[0], "hidden"); + } else { + addClass(correctionsElem[0], "hidden"); + } } else if (nb !== 0) { printTab(0); } @@ -191,6 +199,13 @@ function initSearch(rawSearchIndex) { */ let searchIndex; let currentResults; + /** + * Map from normalized type names to integers. Used to make type search + * more efficient. + * + * @type {Map<string, integer>} + */ + let typeNameIdMap; const ALIASES = new Map(); function isWhitespace(c) { @@ -358,6 +373,7 @@ function initSearch(rawSearchIndex) { parserState.typeFilter = null; return { name: name, + id: -1, fullPath: pathSegments, pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1), pathLast: pathSegments[pathSegments.length - 1], @@ -718,6 +734,7 @@ function initSearch(rawSearchIndex) { foundElems: 0, literalSearch: false, error: null, + correction: null, }; } @@ -873,7 +890,7 @@ function initSearch(rawSearchIndex) { * * @param {Array<Result>} results_in_args * @param {Array<Result>} results_returned - * @param {Array<Result>} results_in_args + * @param {Array<Result>} results_others * @param {ParsedQuery} parsedQuery * * @return {ResultsTable} @@ -1091,48 +1108,50 @@ function initSearch(rawSearchIndex) { * * @param {Row} row - The object to check. * @param {QueryElement} elem - The element from the parsed query. - * @param {integer} defaultDistance - This is the value to return in case there are no - * generics. * - * @return {integer} - Returns the best match (if any) or `maxEditDistance + 1`. + * @return {boolean} - Returns true if a match, false otherwise. */ - function checkGenerics(row, elem, defaultDistance, maxEditDistance) { - if (row.generics.length === 0) { - return elem.generics.length === 0 ? defaultDistance : maxEditDistance + 1; - } else if (row.generics.length > 0 && row.generics[0].name === null) { - return checkGenerics(row.generics[0], elem, defaultDistance, maxEditDistance); - } - // The names match, but we need to be sure that all generics kinda - // match as well. + function checkGenerics(row, elem) { + if (row.generics.length === 0 || elem.generics.length === 0) { + return false; + } + // This function is called if the names match, but we need to make + // sure that all generics match as well. + // + // This search engine implements order-agnostic unification. There + // should be no missing duplicates (generics have "bag semantics"), + // and the row is allowed to have extras. if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) { const elems = new Map(); - for (const entry of row.generics) { - if (entry.name === "") { + const addEntryToElems = function addEntryToElems(entry) { + if (entry.id === -1) { // Pure generic, needs to check into it. - if (checkGenerics(entry, elem, maxEditDistance + 1, maxEditDistance) - !== 0) { - return maxEditDistance + 1; + for (const inner_entry of entry.generics) { + addEntryToElems(inner_entry); } - continue; + return; } let currentEntryElems; - if (elems.has(entry.name)) { - currentEntryElems = elems.get(entry.name); + if (elems.has(entry.id)) { + currentEntryElems = elems.get(entry.id); } else { currentEntryElems = []; - elems.set(entry.name, currentEntryElems); + elems.set(entry.id, currentEntryElems); } currentEntryElems.push(entry); + }; + for (const entry of row.generics) { + addEntryToElems(entry); } // We need to find the type that matches the most to remove it in order // to move forward. const handleGeneric = generic => { - if (!elems.has(generic.name)) { + if (!elems.has(generic.id)) { return false; } - const matchElems = elems.get(generic.name); + const matchElems = elems.get(generic.id); const matchIdx = matchElems.findIndex(tmp_elem => { - if (checkGenerics(tmp_elem, generic, 0, maxEditDistance) !== 0) { + if (generic.generics.length > 0 && !checkGenerics(tmp_elem, generic)) { return false; } return typePassesFilter(generic.typeFilter, tmp_elem.ty); @@ -1142,7 +1161,7 @@ function initSearch(rawSearchIndex) { } matchElems.splice(matchIdx, 1); if (matchElems.length === 0) { - elems.delete(generic.name); + elems.delete(generic.id); } return true; }; @@ -1152,17 +1171,17 @@ function initSearch(rawSearchIndex) { // own type. for (const generic of elem.generics) { if (generic.typeFilter !== -1 && !handleGeneric(generic)) { - return maxEditDistance + 1; + return false; } } for (const generic of elem.generics) { if (generic.typeFilter === -1 && !handleGeneric(generic)) { - return maxEditDistance + 1; + return false; } } - return 0; + return true; } - return maxEditDistance + 1; + return false; } /** @@ -1172,17 +1191,15 @@ function initSearch(rawSearchIndex) { * @param {Row} row * @param {QueryElement} elem - The element from the parsed query. * - * @return {integer} - Returns an edit distance to the best match. + * @return {boolean} - Returns true if found, false otherwise. */ - function checkIfInGenerics(row, elem, maxEditDistance) { - let dist = maxEditDistance + 1; + function checkIfInGenerics(row, elem) { for (const entry of row.generics) { - dist = Math.min(checkType(entry, elem, true, maxEditDistance), dist); - if (dist === 0) { - break; + if (checkType(entry, elem)) { + return true; } } - return dist; + return false; } /** @@ -1191,75 +1208,26 @@ function initSearch(rawSearchIndex) { * * @param {Row} row * @param {QueryElement} elem - The element from the parsed query. - * @param {boolean} literalSearch * - * @return {integer} - Returns an edit distance to the best match. If there is - * no match, returns `maxEditDistance + 1`. + * @return {boolean} - Returns true if the type matches, false otherwise. */ - function checkType(row, elem, literalSearch, maxEditDistance) { - if (row.name === null) { + function checkType(row, elem) { + if (row.id === -1) { // This is a pure "generic" search, no need to run other checks. - if (row.generics.length > 0) { - return checkIfInGenerics(row, elem, maxEditDistance); - } - return maxEditDistance + 1; + return row.generics.length > 0 ? checkIfInGenerics(row, elem) : false; } - let dist; - if (typePassesFilter(elem.typeFilter, row.ty)) { - dist = editDistance(row.name, elem.name, maxEditDistance); - } else { - dist = maxEditDistance + 1; - } - if (literalSearch) { - if (dist !== 0) { - // The name didn't match, let's try to check if the generics do. - if (elem.generics.length === 0) { - const checkGeneric = row.generics.length > 0; - if (checkGeneric && row.generics - .findIndex(tmp_elem => tmp_elem.name === elem.name && - typePassesFilter(elem.typeFilter, tmp_elem.ty)) !== -1) { - return 0; - } - } - return maxEditDistance + 1; - } else if (elem.generics.length > 0) { - return checkGenerics(row, elem, maxEditDistance + 1, maxEditDistance); - } - return 0; - } else if (row.generics.length > 0) { - if (elem.generics.length === 0) { - if (dist === 0) { - return 0; - } - // The name didn't match so we now check if the type we're looking for is inside - // the generics! - dist = Math.min(dist, checkIfInGenerics(row, elem, maxEditDistance)); - return dist; - } else if (dist > maxEditDistance) { - // So our item's name doesn't match at all and has generics. - // - // Maybe it's present in a sub generic? For example "f<A<B<C>>>()", if we're - // looking for "B<C>", we'll need to go down. - return checkIfInGenerics(row, elem, maxEditDistance); - } else { - // At this point, the name kinda match and we have generics to check, so - // let's go! - const tmp_dist = checkGenerics(row, elem, dist, maxEditDistance); - if (tmp_dist > maxEditDistance) { - return maxEditDistance + 1; - } - // We compute the median value of both checks and return it. - return (tmp_dist + dist) / 2; + if (row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty)) { + if (elem.generics.length > 0) { + return checkGenerics(row, elem); } - } else if (elem.generics.length > 0) { - // In this case, we were expecting generics but there isn't so we simply reject this - // one. - return maxEditDistance + 1; + return true; } - // No generics on our query or on the target type so we can return without doing - // anything else. - return dist; + + // If the current item does not match, try [unboxing] the generic. + // [unboxing]: + // https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf + return checkIfInGenerics(row, elem); } /** @@ -1267,17 +1235,11 @@ function initSearch(rawSearchIndex) { * * @param {Row} row * @param {QueryElement} elem - The element from the parsed query. - * @param {integer} maxEditDistance * @param {Array<integer>} skipPositions - Do not return one of these positions. * - * @return {dist: integer, position: integer} - Returns an edit distance to the best match. - * If there is no match, returns - * `maxEditDistance + 1` and position: -1. + * @return {integer} - Returns the position of the match, or -1 if none. */ - function findArg(row, elem, maxEditDistance, skipPositions) { - let dist = maxEditDistance + 1; - let position = -1; - + function findArg(row, elem, skipPositions) { if (row && row.type && row.type.inputs && row.type.inputs.length > 0) { let i = 0; for (const input of row.type.inputs) { @@ -1285,24 +1247,13 @@ function initSearch(rawSearchIndex) { i += 1; continue; } - const typeDist = checkType( - input, - elem, - parsedQuery.literalSearch, - maxEditDistance - ); - if (typeDist === 0) { - return {dist: 0, position: i}; - } - if (typeDist < dist) { - dist = typeDist; - position = i; + if (checkType(input, elem)) { + return i; } i += 1; } } - dist = parsedQuery.literalSearch ? maxEditDistance + 1 : dist; - return {dist, position}; + return -1; } /** @@ -1310,43 +1261,25 @@ function initSearch(rawSearchIndex) { * * @param {Row} row * @param {QueryElement} elem - The element from the parsed query. - * @param {integer} maxEditDistance * @param {Array<integer>} skipPositions - Do not return one of these positions. * - * @return {dist: integer, position: integer} - Returns an edit distance to the best match. - * If there is no match, returns - * `maxEditDistance + 1` and position: -1. + * @return {integer} - Returns the position of the matching item, or -1 if none. */ - function checkReturned(row, elem, maxEditDistance, skipPositions) { - let dist = maxEditDistance + 1; - let position = -1; - + function checkReturned(row, elem, skipPositions) { if (row && row.type && row.type.output.length > 0) { - const ret = row.type.output; let i = 0; - for (const ret_ty of ret) { + for (const ret_ty of row.type.output) { if (skipPositions.indexOf(i) !== -1) { i += 1; continue; } - const typeDist = checkType( - ret_ty, - elem, - parsedQuery.literalSearch, - maxEditDistance - ); - if (typeDist === 0) { - return {dist: 0, position: i}; - } - if (typeDist < dist) { - dist = typeDist; - position = i; + if (checkType(ret_ty, elem)) { + return i; } i += 1; } } - dist = parsedQuery.literalSearch ? maxEditDistance + 1 : dist; - return {dist, position}; + return -1; } function checkPath(contains, ty, maxEditDistance) { @@ -1543,17 +1476,20 @@ function initSearch(rawSearchIndex) { if (!row || (filterCrates !== null && row.crate !== filterCrates)) { return; } - let dist, index = -1, path_dist = 0; + let index = -1, path_dist = 0; const fullId = row.id; const searchWord = searchWords[pos]; - const in_args = findArg(row, elem, maxEditDistance, []); - const returned = checkReturned(row, elem, maxEditDistance, []); - - // path_dist is 0 because no parent path information is currently stored - // in the search index - addIntoResults(results_in_args, fullId, pos, -1, in_args.dist, 0, maxEditDistance); - addIntoResults(results_returned, fullId, pos, -1, returned.dist, 0, maxEditDistance); + const in_args = findArg(row, elem, []); + if (in_args !== -1) { + // path_dist is 0 because no parent path information is currently stored + // in the search index + addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance); + } + const returned = checkReturned(row, elem, []); + if (returned !== -1) { + addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance); + } if (!typePassesFilter(elem.typeFilter, row.ty)) { return; @@ -1574,16 +1510,6 @@ function initSearch(rawSearchIndex) { index = row_index; } - // No need to check anything else if it's a "pure" generics search. - if (elem.name.length === 0) { - if (row.type !== null) { - dist = checkGenerics(row.type, elem, maxEditDistance + 1, maxEditDistance); - // path_dist is 0 because we know it's empty - addIntoResults(results_others, fullId, pos, index, dist, 0, maxEditDistance); - } - return; - } - if (elem.fullPath.length > 1) { path_dist = checkPath(elem.pathWithoutLast, row, maxEditDistance); if (path_dist > maxEditDistance) { @@ -1598,7 +1524,7 @@ function initSearch(rawSearchIndex) { return; } - dist = editDistance(searchWord, elem.pathLast, maxEditDistance); + const dist = editDistance(searchWord, elem.pathLast, maxEditDistance); if (index === -1 && dist + path_dist > maxEditDistance) { return; @@ -1616,28 +1542,22 @@ function initSearch(rawSearchIndex) { * @param {integer} pos - Position in the `searchIndex`. * @param {Object} results */ - function handleArgs(row, pos, results, maxEditDistance) { + function handleArgs(row, pos, results) { if (!row || (filterCrates !== null && row.crate !== filterCrates)) { return; } - let totalDist = 0; - let nbDist = 0; - // If the result is too "bad", we return false and it ends this search. function checkArgs(elems, callback) { const skipPositions = []; for (const elem of elems) { // There is more than one parameter to the query so all checks should be "exact" - const { dist, position } = callback( + const position = callback( row, elem, - maxEditDistance, skipPositions ); - if (dist <= 1) { - nbDist += 1; - totalDist += dist; + if (position !== -1) { skipPositions.push(position); } else { return false; @@ -1652,11 +1572,7 @@ function initSearch(rawSearchIndex) { return; } - if (nbDist === 0) { - return; - } - const dist = Math.round(totalDist / nbDist); - addIntoResults(results, row.id, pos, 0, dist, 0, maxEditDistance); + addIntoResults(results, row.id, pos, 0, 0, 0, Number.MAX_VALUE); } function innerRunQuery() { @@ -1671,6 +1587,53 @@ function initSearch(rawSearchIndex) { } const maxEditDistance = Math.floor(queryLen / 3); + /** + * Convert names to ids in parsed query elements. + * This is not used for the "In Names" tab, but is used for the + * "In Params", "In Returns", and "In Function Signature" tabs. + * + * If there is no matching item, but a close-enough match, this + * function also that correction. + * + * See `buildTypeMapIndex` for more information. + * + * @param {QueryElement} elem + */ + function convertNameToId(elem) { + if (typeNameIdMap.has(elem.name)) { + elem.id = typeNameIdMap.get(elem.name); + } else if (!parsedQuery.literalSearch) { + let match = -1; + let matchDist = maxEditDistance + 1; + let matchName = ""; + for (const [name, id] of typeNameIdMap) { + const dist = editDistance(name, elem.name, maxEditDistance); + if (dist <= matchDist && dist <= maxEditDistance) { + if (dist === matchDist && matchName > name) { + continue; + } + match = id; + matchDist = dist; + matchName = name; + } + } + if (match !== -1) { + parsedQuery.correction = matchName; + } + elem.id = match; + } + for (const elem2 of elem.generics) { + convertNameToId(elem2); + } + } + + for (const elem of parsedQuery.elems) { + convertNameToId(elem); + } + for (const elem of parsedQuery.returned) { + convertNameToId(elem); + } + if (parsedQuery.foundElems === 1) { if (parsedQuery.elems.length === 1) { elem = parsedQuery.elems[0]; @@ -1695,22 +1658,23 @@ function initSearch(rawSearchIndex) { in_returned = checkReturned( row, elem, - maxEditDistance, [] ); - addIntoResults( - results_others, - row.id, - i, - -1, - in_returned.dist, - maxEditDistance - ); + if (in_returned !== -1) { + addIntoResults( + results_others, + row.id, + i, + -1, + 0, + Number.MAX_VALUE + ); + } } } } else if (parsedQuery.foundElems > 0) { for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) { - handleArgs(searchIndex[i], i, results_others, maxEditDistance); + handleArgs(searchIndex[i], i, results_others); } } } @@ -2030,6 +1994,16 @@ function initSearch(rawSearchIndex) { currentTab = 0; } + if (results.query.correction !== null) { + const orig = results.query.returned.length > 0 + ? results.query.returned[0].name + : results.query.elems[0].name; + output += "<h3 class=\"search-corrections\">" + + `Type "${orig}" not found. ` + + "Showing results for closest type name " + + `"${results.query.correction}" instead.</h3>`; + } + const resultsElem = document.createElement("div"); resultsElem.id = "results"; resultsElem.appendChild(ret_others[0]); @@ -2109,6 +2083,34 @@ function initSearch(rawSearchIndex) { } /** + * Add an item to the type Name->ID map, or, if one already exists, use it. + * Returns the number. If name is "" or null, return -1 (pure generic). + * + * This is effectively string interning, so that function matching can be + * done more quickly. Two types with the same name but different item kinds + * get the same ID. + * + * @param {Map<string, integer>} typeNameIdMap + * @param {string} name + * + * @returns {integer} + */ + function buildTypeMapIndex(typeNameIdMap, name) { + + if (name === "" || name === null) { + return -1; + } + + if (typeNameIdMap.has(name)) { + return typeNameIdMap.get(name); + } else { + const id = typeNameIdMap.size; + typeNameIdMap.set(name, id); + return id; + } + } + + /** * Convert a list of RawFunctionType / ID to object-based FunctionType. * * Crates often have lots of functions in them, and it's common to have a large number of @@ -2126,7 +2128,7 @@ function initSearch(rawSearchIndex) { * * @return {Array<FunctionSearchType>} */ - function buildItemSearchTypeAll(types, lowercasePaths) { + function buildItemSearchTypeAll(types, lowercasePaths, typeNameIdMap) { const PATH_INDEX_DATA = 0; const GENERICS_DATA = 1; return types.map(type => { @@ -2136,11 +2138,17 @@ function initSearch(rawSearchIndex) { generics = []; } else { pathIndex = type[PATH_INDEX_DATA]; - generics = buildItemSearchTypeAll(type[GENERICS_DATA], lowercasePaths); + generics = buildItemSearchTypeAll( + type[GENERICS_DATA], + lowercasePaths, + typeNameIdMap + ); } return { // `0` is used as a sentinel because it's fewer bytes than `null` - name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name, + id: pathIndex === 0 + ? -1 + : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name), ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty, generics: generics, }; @@ -2159,10 +2167,11 @@ function initSearch(rawSearchIndex) { * * @param {RawFunctionSearchType} functionSearchType * @param {Array<{name: string, ty: number}>} lowercasePaths + * @param {Map<string, integer>} * * @return {null|FunctionSearchType} */ - function buildFunctionSearchType(functionSearchType, lowercasePaths) { + function buildFunctionSearchType(functionSearchType, lowercasePaths, typeNameIdMap) { const INPUTS_DATA = 0; const OUTPUT_DATA = 1; // `0` is used as a sentinel because it's fewer bytes than `null` @@ -2173,23 +2182,35 @@ function initSearch(rawSearchIndex) { if (typeof functionSearchType[INPUTS_DATA] === "number") { const pathIndex = functionSearchType[INPUTS_DATA]; inputs = [{ - name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name, + id: pathIndex === 0 + ? -1 + : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name), ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty, generics: [], }]; } else { - inputs = buildItemSearchTypeAll(functionSearchType[INPUTS_DATA], lowercasePaths); + inputs = buildItemSearchTypeAll( + functionSearchType[INPUTS_DATA], + lowercasePaths, + typeNameIdMap + ); } if (functionSearchType.length > 1) { if (typeof functionSearchType[OUTPUT_DATA] === "number") { const pathIndex = functionSearchType[OUTPUT_DATA]; output = [{ - name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name, + id: pathIndex === 0 + ? -1 + : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name), ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty, generics: [], }]; } else { - output = buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA], lowercasePaths); + output = buildItemSearchTypeAll( + functionSearchType[OUTPUT_DATA], + lowercasePaths, + typeNameIdMap + ); } } else { output = []; @@ -2202,9 +2223,12 @@ function initSearch(rawSearchIndex) { function buildIndex(rawSearchIndex) { searchIndex = []; /** + * List of normalized search words (ASCII lowercased, and undescores removed). + * * @type {Array<string>} */ const searchWords = []; + typeNameIdMap = new Map(); const charA = "A".charCodeAt(0); let currentIndex = 0; let id = 0; @@ -2337,7 +2361,11 @@ function initSearch(rawSearchIndex) { path: itemPaths.has(i) ? itemPaths.get(i) : lastPath, desc: itemDescs[i], parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, - type: buildFunctionSearchType(itemFunctionSearchTypes[i], lowercasePaths), + type: buildFunctionSearchType( + itemFunctionSearchTypes[i], + lowercasePaths, + typeNameIdMap + ), id: id, normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""), deprecated: deprecatedItems.has(i), @@ -2412,10 +2440,6 @@ function initSearch(rawSearchIndex) { const searchAfter500ms = () => { searchState.clearInputTimeout(); if (searchState.input.value.length === 0) { - if (browserSupportsHistoryApi()) { - history.replaceState(null, window.currentCrate + " - Rust", - getNakedUrl() + window.location.hash); - } searchState.hideResults(); } else { searchState.timeout = setTimeout(search, 500); diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index ebbe6c1ca..2cba32c1b 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -1,5 +1,5 @@ // Local js definitions: -/* global getSettingValue, getVirtualKey, updateLocalStorage, updateTheme */ +/* global getSettingValue, updateLocalStorage, updateTheme */ /* global addClass, removeClass, onEach, onEachLazy, blurHandler, elemIsInParent */ /* global MAIN_ID, getVar, getSettingsButton */ @@ -32,21 +32,6 @@ } } - function handleKey(ev) { - // Don't interfere with browser shortcuts - if (ev.ctrlKey || ev.altKey || ev.metaKey) { - return; - } - switch (getVirtualKey(ev)) { - case "Enter": - case "Return": - case "Space": - ev.target.checked = !ev.target.checked; - ev.preventDefault(); - break; - } - } - function showLightAndDark() { removeClass(document.getElementById("preferred-light-theme"), "hidden"); removeClass(document.getElementById("preferred-dark-theme"), "hidden"); @@ -77,8 +62,6 @@ toggle.onchange = function() { changeSetting(this.id, this.checked); }; - toggle.onkeyup = handleKey; - toggle.onkeyrelease = handleKey; }); onEachLazy(settingsElement.querySelectorAll("input[type=\"radio\"]"), elem => { const settingId = elem.name; diff --git a/src/librustdoc/html/static/js/source-script.js b/src/librustdoc/html/static/js/source-script.js index 9aa755173..d999f3b36 100644 --- a/src/librustdoc/html/static/js/source-script.js +++ b/src/librustdoc/html/static/js/source-script.js @@ -52,12 +52,12 @@ function createDirEntry(elem, parent, fullPath, hasFoundFile) { const files = document.createElement("div"); files.className = "files"; if (elem[FILES_OFFSET]) { + const w = window.location.href.split("#")[0]; for (const file_text of elem[FILES_OFFSET]) { const file = document.createElement("a"); file.innerText = file_text; file.href = rootPath + "src/" + fullPath + file_text + ".html"; file.addEventListener("click", closeSidebarIfMobile); - const w = window.location.href.split("#")[0]; if (!hasFoundFile && w === file.href) { file.className = "selected"; dirEntry.open = true; diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html index a01457971..c21967005 100644 --- a/src/librustdoc/html/templates/item_union.html +++ b/src/librustdoc/html/templates/item_union.html @@ -1,8 +1,8 @@ <pre class="rust item-decl"><code> - {{ self.render_attributes_in_pre() | safe }} + {{ self::item_template_render_attributes_in_pre(self.borrow()) | safe }} {{ self.render_union() | safe }} </code></pre> -{{ self.document() | safe }} +{{ self::item_template_document(self.borrow()) | safe }} {% if self.fields_iter().peek().is_some() %} <h2 id="fields" class="fields small-section-header"> Fields<a href="#fields" class="anchor">§</a> @@ -19,5 +19,5 @@ {{ self.document_field(field) | safe }} {% endfor %} {% endif %} -{{ self.render_assoc_items() | safe }} -{{ self.document_type_layout() | safe }} +{{ self::item_template_render_assoc_items(self.borrow()) | safe }} +{{ self::item_template_document_type_layout(self.borrow()) | safe }} diff --git a/src/librustdoc/html/templates/type_layout.html b/src/librustdoc/html/templates/type_layout.html new file mode 100644 index 000000000..20e09a548 --- /dev/null +++ b/src/librustdoc/html/templates/type_layout.html @@ -0,0 +1,58 @@ +<h2 id="layout" class="small-section-header"> {# #} + Layout<a href="#layout" class="anchor">§</a> {# #} +</h2> {# #} +<div class="docblock"> {# #} + {% match type_layout_size %} + {% when Ok(type_layout_size) %} + <div class="warning"> {# #} + <p> {# #} + <strong>Note:</strong> Most layout information is <strong>completely {#+ #} + unstable</strong> and may even differ between compilations. {#+ #} + The only exception is types with certain <code>repr(...)</code> {#+ #} + attributes. Please see the Rust Reference’s {#+ #} + <a href="https://doc.rust-lang.org/reference/type-layout.html">“Type Layout”</a> {#+ #} + chapter for details on type layout guarantees. {# #} + </p> {# #} + </div> {# #} + <p><strong>Size:</strong> {{ type_layout_size|safe }}</p> {# #} + {% if !variants.is_empty() %} + <p> {# #} + <strong>Size for each variant:</strong> {# #} + </p> {# #} + <ul> {# #} + {% for (name, layout_size) in variants %} + <li> {# #} + <code>{{ name }}</code>: {#+ #} + {{ layout_size|safe }} + </li> {# #} + {% endfor %} + </ul> {# #} + {% endif %} + {# This kind of layout error can occur with valid code, e.g. if you try to + get the layout of a generic type such as `Vec<T>`. #} + {% when Err(LayoutError::Unknown(_)) %} + <p> {# #} + <strong>Note:</strong> Unable to compute type layout, {#+ #} + possibly due to this type having generic parameters. {#+ #} + Layout can only be computed for concrete, fully-instantiated types. {# #} + </p> {# #} + {# This kind of error probably can't happen with valid code, but we don't + want to panic and prevent the docs from building, so we just let the + user know that we couldn't compute the layout. #} + {% when Err(LayoutError::SizeOverflow(_)) %} + <p> {# #} + <strong>Note:</strong> Encountered an error during type layout; {#+ #} + the type was too big. {# #} + </p> {# #} + {% when Err(LayoutError::NormalizationFailure(_, _)) %} + <p> {# #} + <strong>Note:</strong> Encountered an error during type layout; {#+ #} + the type failed to be normalized. {# #} + </p> {# #} + {% when Err(LayoutError::Cycle) %} + <p> {# #} + <strong>Note:</strong> Encountered an error during type layout; {#+ #} + the type's layout depended on the type's layout itself. {# #} + </p> {# #} + {% endmatch %} +</div> {# #} diff --git a/src/librustdoc/html/templates/type_layout_size.html b/src/librustdoc/html/templates/type_layout_size.html new file mode 100644 index 000000000..9c2b39edc --- /dev/null +++ b/src/librustdoc/html/templates/type_layout_size.html @@ -0,0 +1,12 @@ +{% if is_unsized %} + (unsized) +{% else %} + {% if size == 1 %} + 1 byte + {% else %} + {{ size +}} bytes + {% endif %} + {% if is_uninhabited %} + {# +#} (<a href="https://doc.rust-lang.org/stable/reference/glossary.html#uninhabited">uninhabited</a>) + {% endif %} +{% endif %} |