summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/html
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/html')
-rw-r--r--src/librustdoc/html/escape.rs36
-rw-r--r--src/librustdoc/html/format.rs59
-rw-r--r--src/librustdoc/html/highlight.rs47
-rw-r--r--src/librustdoc/html/highlight/fixtures/dos_line.html2
-rw-r--r--src/librustdoc/html/highlight/fixtures/sample.html8
-rw-r--r--src/librustdoc/html/markdown.rs114
-rw-r--r--src/librustdoc/html/render/context.rs21
-rw-r--r--src/librustdoc/html/render/mod.rs89
-rw-r--r--src/librustdoc/html/render/print_item.rs107
-rw-r--r--src/librustdoc/html/render/search_index.rs433
-rw-r--r--src/librustdoc/html/render/sidebar.rs24
-rw-r--r--src/librustdoc/html/render/span_map.rs4
-rw-r--r--src/librustdoc/html/render/type_layout.rs59
-rw-r--r--src/librustdoc/html/render/write_shared.rs33
-rw-r--r--src/librustdoc/html/sources.rs8
-rw-r--r--src/librustdoc/html/static/css/noscript.css6
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css211
-rw-r--r--src/librustdoc/html/static/js/externs.js6
-rw-r--r--src/librustdoc/html/static/js/main.js303
-rw-r--r--src/librustdoc/html/static/js/search.js1327
-rw-r--r--src/librustdoc/html/static/js/settings.js23
-rw-r--r--src/librustdoc/html/static/js/src-script.js50
-rw-r--r--src/librustdoc/html/static/js/storage.js53
-rw-r--r--src/librustdoc/html/templates/item_union.html4
-rw-r--r--src/librustdoc/html/templates/page.html10
-rw-r--r--src/librustdoc/html/templates/type_layout.html4
26 files changed, 2093 insertions, 948 deletions
diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs
index 4a19d0a44..ea4b573ae 100644
--- a/src/librustdoc/html/escape.rs
+++ b/src/librustdoc/html/escape.rs
@@ -38,3 +38,39 @@ impl<'a> fmt::Display for Escape<'a> {
Ok(())
}
}
+
+/// Wrapper struct which will emit the HTML-escaped version of the contained
+/// string when passed to a format string.
+///
+/// This is only safe to use for text nodes. If you need your output to be
+/// safely contained in an attribute, use [`Escape`]. If you don't know the
+/// difference, use [`Escape`].
+pub(crate) struct EscapeBodyText<'a>(pub &'a str);
+
+impl<'a> fmt::Display for EscapeBodyText<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // Because the internet is always right, turns out there's not that many
+ // characters to escape: http://stackoverflow.com/questions/7381974
+ let EscapeBodyText(s) = *self;
+ let pile_o_bits = s;
+ let mut last = 0;
+ for (i, ch) in s.char_indices() {
+ let s = match ch {
+ '>' => "&gt;",
+ '<' => "&lt;",
+ '&' => "&amp;",
+ _ => continue,
+ };
+ fmt.write_str(&pile_o_bits[last..i])?;
+ fmt.write_str(s)?;
+ // NOTE: we only expect single byte characters here - which is fine as long as we
+ // only match single byte characters
+ last = i + 1;
+ }
+
+ if last < s.len() {
+ fmt.write_str(&pile_o_bits[last..])?;
+ }
+ Ok(())
+ }
+}
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 29fd880af..a9c0ab557 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -367,7 +367,7 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
if ending == Ending::Newline {
let mut clause = " ".repeat(indent.saturating_sub(1));
- write!(clause, "<span class=\"where fmt-newline\">where{where_preds},</span>")?;
+ write!(clause, "<div class=\"where\">where{where_preds},</div>")?;
clause
} else {
// insert a newline after a single space but before multiple spaces at the start
@@ -1166,13 +1166,17 @@ fn fmt_type<'cx>(
// we need to surround them with angle brackets in some cases (e.g. `<dyn …>::P`).
if f.alternate() {
- if let Some(trait_) = trait_ && 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 let Some(trait_) = trait_ && should_show_cast {
+ if let Some(trait_) = trait_
+ && should_show_cast
+ {
write!(f, "&lt;{} as {}&gt;::", self_type.print(cx), trait_.print(cx))?
} else {
write!(f, "{}::", self_type.print(cx))?
@@ -1268,16 +1272,23 @@ impl clean::Impl {
write!(f, " for ")?;
}
- if let clean::Type::Tuple(types) = &self.for_ &&
- let [clean::Type::Generic(name)] = &types[..] &&
- (self.kind.is_fake_variadic() || self.kind.is_auto())
+ if let clean::Type::Tuple(types) = &self.for_
+ && let [clean::Type::Generic(name)] = &types[..]
+ && (self.kind.is_fake_variadic() || self.kind.is_auto())
{
// Hardcoded anchor library/core/src/primitive_docs.rs
// Link should match `# Trait implementations`
- primitive_link_fragment(f, PrimitiveType::Tuple, format_args!("({name}₁, {name}₂, …, {name}ₙ)"), "#trait-implementations-1", cx)?;
- } else if let clean::BareFunction(bare_fn) = &self.for_ &&
- let [clean::Argument { type_: clean::Type::Generic(name), .. }] = &bare_fn.decl.inputs.values[..] &&
- (self.kind.is_fake_variadic() || self.kind.is_auto())
+ primitive_link_fragment(
+ f,
+ PrimitiveType::Tuple,
+ format_args!("({name}₁, {name}₂, …, {name}ₙ)"),
+ "#trait-implementations-1",
+ cx,
+ )?;
+ } else if let clean::BareFunction(bare_fn) = &self.for_
+ && let [clean::Argument { type_: clean::Type::Generic(name), .. }] =
+ &bare_fn.decl.inputs.values[..]
+ && (self.kind.is_fake_variadic() || self.kind.is_auto())
{
// Hardcoded anchor library/core/src/primitive_docs.rs
// Link should match `# Trait implementations`
@@ -1286,22 +1297,18 @@ impl clean::Impl {
let unsafety = bare_fn.unsafety.print_with_space();
let abi = print_abi_with_space(bare_fn.abi);
if f.alternate() {
- write!(
- f,
- "{hrtb:#}{unsafety}{abi:#}",
- )?;
+ write!(f, "{hrtb:#}{unsafety}{abi:#}",)?;
} else {
- write!(
- f,
- "{hrtb}{unsafety}{abi}",
- )?;
+ write!(f, "{hrtb}{unsafety}{abi}",)?;
}
- let ellipsis = if bare_fn.decl.c_variadic {
- ", ..."
- } else {
- ""
- };
- primitive_link_fragment(f, PrimitiveType::Tuple, format_args!("fn ({name}₁, {name}₂, …, {name}ₙ{ellipsis})"), "#trait-implementations-1", cx)?;
+ let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" };
+ primitive_link_fragment(
+ f,
+ PrimitiveType::Tuple,
+ format_args!("fn({name}₁, {name}₂, …, {name}ₙ{ellipsis})"),
+ "#trait-implementations-1",
+ cx,
+ )?;
// Write output.
if !bare_fn.decl.output.is_unit() {
write!(f, " -> ")?;
@@ -1447,7 +1454,9 @@ impl clean::FnDecl {
let amp = if f.alternate() { "&" } else { "&amp;" };
write!(f, "(")?;
- if let Some(n) = line_wrapping_indent && !self.inputs.values.is_empty() {
+ if let Some(n) = line_wrapping_indent
+ && !self.inputs.values.is_empty()
+ {
write!(f, "\n{}", Indent(n + 4))?;
}
for (i, input) in self.inputs.values.iter().enumerate() {
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index d8e36139a..1cdc792a8 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -6,7 +6,7 @@
//! Use the `render_with_highlighting` to highlight some rust code.
use crate::clean::PrimitiveType;
-use crate::html::escape::Escape;
+use crate::html::escape::EscapeBodyText;
use crate::html::render::{Context, LinkFromSrc};
use std::collections::VecDeque;
@@ -185,22 +185,30 @@ impl<'a, 'tcx, F: Write> TokenHandler<'a, 'tcx, F> {
if self.pending_elems.is_empty() {
return false;
}
- if let Some((_, parent_class)) = self.closing_tags.last() &&
- can_merge(current_class, Some(*parent_class), "")
+ if let Some((_, parent_class)) = self.closing_tags.last()
+ && can_merge(current_class, Some(*parent_class), "")
{
for (text, class) in self.pending_elems.iter() {
- string(self.out, Escape(text), *class, &self.href_context, false);
+ string(self.out, EscapeBodyText(text), *class, &self.href_context, false);
}
} else {
// We only want to "open" the tag ourselves if we have more than one pending and if the
// current parent tag is not the same as our pending content.
- let close_tag = if self.pending_elems.len() > 1 && let Some(current_class) = current_class {
+ let close_tag = if self.pending_elems.len() > 1
+ && let Some(current_class) = current_class
+ {
Some(enter_span(self.out, current_class, &self.href_context))
} else {
None
};
for (text, class) in self.pending_elems.iter() {
- string(self.out, Escape(text), *class, &self.href_context, close_tag.is_none());
+ string(
+ self.out,
+ EscapeBodyText(text),
+ *class,
+ &self.href_context,
+ close_tag.is_none(),
+ );
}
if let Some(close_tag) = close_tag {
exit_span(self.out, close_tag);
@@ -260,10 +268,12 @@ pub(super) fn write_code(
Highlight::Token { text, class } => {
// If we received a `ExitSpan` event and then have a non-compatible `Class`, we
// need to close the `<span>`.
- let need_current_class_update = if let Some(pending) = token_handler.pending_exit_span &&
- !can_merge(Some(pending), class, text) {
- token_handler.handle_exit_span();
- true
+ let need_current_class_update = if let Some(pending) =
+ token_handler.pending_exit_span
+ && !can_merge(Some(pending), class, text)
+ {
+ token_handler.handle_exit_span();
+ true
// If the two `Class` are different, time to flush the current content and start
// a new one.
} else if !can_merge(token_handler.current_class, class, text) {
@@ -293,7 +303,8 @@ pub(super) fn write_code(
}
}
if should_add {
- let closing_tag = enter_span(token_handler.out, class, &token_handler.href_context);
+ let closing_tag =
+ enter_span(token_handler.out, class, &token_handler.href_context);
token_handler.closing_tags.push((closing_tag, class));
}
@@ -302,8 +313,14 @@ pub(super) fn write_code(
}
Highlight::ExitSpan => {
token_handler.current_class = None;
- token_handler.pending_exit_span =
- Some(token_handler.closing_tags.last().as_ref().expect("ExitSpan without EnterSpan").1);
+ token_handler.pending_exit_span = Some(
+ token_handler
+ .closing_tags
+ .last()
+ .as_ref()
+ .expect("ExitSpan without EnterSpan")
+ .1,
+ );
}
};
});
@@ -466,7 +483,9 @@ impl<'a> PeekIter<'a> {
}
/// Returns the next item after the current one. It doesn't interfere with `peek_next` output.
fn peek(&mut self) -> Option<&(TokenKind, &'a str)> {
- if self.stored.is_empty() && let Some(next) = self.iter.next() {
+ if self.stored.is_empty()
+ && let Some(next) = self.iter.next()
+ {
self.stored.push_back(next);
}
self.stored.front()
diff --git a/src/librustdoc/html/highlight/fixtures/dos_line.html b/src/librustdoc/html/highlight/fixtures/dos_line.html
index 30b50ca7c..b98e67125 100644
--- a/src/librustdoc/html/highlight/fixtures/dos_line.html
+++ b/src/librustdoc/html/highlight/fixtures/dos_line.html
@@ -1,3 +1,3 @@
<span class="kw">pub fn </span>foo() {
-<span class="macro">println!</span>(<span class="string">&quot;foo&quot;</span>);
+<span class="macro">println!</span>(<span class="string">"foo"</span>);
}
diff --git a/src/librustdoc/html/highlight/fixtures/sample.html b/src/librustdoc/html/highlight/fixtures/sample.html
index fced2eacd..aa735e815 100644
--- a/src/librustdoc/html/highlight/fixtures/sample.html
+++ b/src/librustdoc/html/highlight/fixtures/sample.html
@@ -8,12 +8,12 @@
.lifetime { color: #B76514; }
.question-mark { color: #ff9011; }
</style>
-<pre><code><span class="attr">#![crate_type = <span class="string">&quot;lib&quot;</span>]
+<pre><code><span class="attr">#![crate_type = <span class="string">"lib"</span>]
</span><span class="kw">use </span>std::path::{Path, PathBuf};
-<span class="attr">#[cfg(target_os = <span class="string">&quot;linux&quot;</span>)]
-#[cfg(target_os = <span class="string">&quot;windows&quot;</span>)]
+<span class="attr">#[cfg(target_os = <span class="string">"linux"</span>)]
+#[cfg(target_os = <span class="string">"windows"</span>)]
</span><span class="kw">fn </span>main() -&gt; () {
<span class="kw">let </span>foo = <span class="bool-val">true </span>&amp;&amp; <span class="bool-val">false </span>|| <span class="bool-val">true</span>;
<span class="kw">let _</span>: <span class="kw-2">*const </span>() = <span class="number">0</span>;
@@ -22,7 +22,7 @@
<span class="kw">let _ </span>= <span class="kw-2">*</span>foo;
<span class="macro">mac!</span>(foo, <span class="kw-2">&amp;mut </span>bar);
<span class="macro">assert!</span>(<span class="self">self</span>.length &lt; N &amp;&amp; index &lt;= <span class="self">self</span>.length);
- ::std::env::var(<span class="string">&quot;gateau&quot;</span>).is_ok();
+ ::std::env::var(<span class="string">"gateau"</span>).is_ok();
<span class="attr">#[rustfmt::skip]
</span><span class="kw">let </span>s:std::path::PathBuf = std::path::PathBuf::new();
<span class="kw">let </span><span class="kw-2">mut </span>s = String::new();
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 2807dfed0..642265f5f 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -27,7 +27,7 @@
//! ```
use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage};
+use rustc_errors::{DiagnosticBuilder, DiagnosticMessage};
use rustc_hir::def_id::DefId;
use rustc_middle::ty::TyCtxt;
pub(crate) use rustc_resolve::rustdoc::main_body_opts;
@@ -234,10 +234,6 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
fn next(&mut self) -> Option<Self::Item> {
let event = self.inner.next();
- let compile_fail;
- let should_panic;
- let ignore;
- let edition;
let Some(Event::Start(Tag::CodeBlock(kind))) = event else {
return event;
};
@@ -253,49 +249,44 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
}
}
- let parse_result = match kind {
- CodeBlockKind::Fenced(ref lang) => {
- let parse_result = LangString::parse_without_check(
- lang,
- self.check_error_codes,
- false,
- self.custom_code_classes_in_docs,
- );
- if !parse_result.rust {
- let added_classes = parse_result.added_classes;
- let lang_string = if let Some(lang) = parse_result.unknown.first() {
- format!("language-{}", lang)
- } else {
- String::new()
- };
- let whitespace = if added_classes.is_empty() { "" } else { " " };
- return Some(Event::Html(
- format!(
- "<div class=\"example-wrap\">\
+ let LangString { added_classes, compile_fail, should_panic, ignore, edition, .. } =
+ match kind {
+ CodeBlockKind::Fenced(ref lang) => {
+ let parse_result = LangString::parse_without_check(
+ lang,
+ self.check_error_codes,
+ false,
+ self.custom_code_classes_in_docs,
+ );
+ if !parse_result.rust {
+ let added_classes = parse_result.added_classes;
+ let lang_string = if let Some(lang) = parse_result.unknown.first() {
+ format!("language-{}", lang)
+ } else {
+ String::new()
+ };
+ let whitespace = if added_classes.is_empty() { "" } else { " " };
+ return Some(Event::Html(
+ format!(
+ "<div class=\"example-wrap\">\
<pre class=\"{lang_string}{whitespace}{added_classes}\">\
<code>{text}</code>\
</pre>\
</div>",
- added_classes = added_classes.join(" "),
- text = Escape(&original_text),
- )
- .into(),
- ));
+ added_classes = added_classes.join(" "),
+ text = Escape(&original_text),
+ )
+ .into(),
+ ));
+ }
+ parse_result
}
- parse_result
- }
- CodeBlockKind::Indented => Default::default(),
- };
+ CodeBlockKind::Indented => Default::default(),
+ };
- let added_classes = parse_result.added_classes;
let lines = original_text.lines().filter_map(|l| map_line(l).for_html());
let text = lines.intersperse("\n".into()).collect::<String>();
- compile_fail = parse_result.compile_fail;
- should_panic = parse_result.should_panic;
- ignore = parse_result.ignore;
- edition = parse_result.edition;
-
let explicit_edition = edition.is_some();
let edition = edition.unwrap_or(self.edition);
@@ -841,10 +832,10 @@ impl<'tcx> ExtraInfo<'tcx> {
if let Some(def_id) = self.def_id.as_local() {
self.tcx.struct_span_lint_hir(
crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
- self.tcx.hir().local_def_id_to_hir_id(def_id),
+ self.tcx.local_def_id_to_hir_id(def_id),
self.sp,
msg,
- |l| l,
+ |_| {},
);
}
}
@@ -852,15 +843,15 @@ impl<'tcx> ExtraInfo<'tcx> {
fn error_invalid_codeblock_attr_with_help(
&self,
msg: impl Into<DiagnosticMessage>,
- help: impl Into<SubdiagnosticMessage>,
+ f: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>),
) {
if let Some(def_id) = self.def_id.as_local() {
self.tcx.struct_span_lint_hir(
crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
- self.tcx.hir().local_def_id_to_hir_id(def_id),
+ self.tcx.local_def_id_to_hir_id(def_id),
self.sp,
msg,
- |lint| lint.help(help),
+ f,
);
}
}
@@ -1119,10 +1110,10 @@ impl<'a, 'tcx> TagIterator<'a, 'tcx> {
return None;
}
let indices = self.parse_string(pos)?;
- if let Some((_, c)) = self.inner.peek().copied() &&
- c != '{' &&
- !is_separator(c) &&
- c != '('
+ if let Some((_, c)) = self.inner.peek().copied()
+ && c != '{'
+ && !is_separator(c)
+ && c != '('
{
self.emit_error(format!("expected ` `, `{{` or `,` after `\"`, found `{c}`"));
return None;
@@ -1294,6 +1285,21 @@ impl LangString {
data.edition = x[7..].parse::<Edition>().ok();
}
LangStringToken::LangToken(x)
+ if x.starts_with("rust") && x[4..].parse::<Edition>().is_ok() =>
+ {
+ if let Some(extra) = extra {
+ extra.error_invalid_codeblock_attr_with_help(
+ format!("unknown attribute `{x}`"),
+ |lint| {
+ lint.help(format!(
+ "there is an attribute with a similar name: `edition{}`",
+ &x[4..],
+ ));
+ },
+ );
+ }
+ }
+ LangStringToken::LangToken(x)
if allow_error_code_check && x.starts_with('E') && x.len() == 5 =>
{
if x[1..].parse::<u32>().is_ok() {
@@ -1337,8 +1343,13 @@ impl LangString {
} {
if let Some(extra) = extra {
extra.error_invalid_codeblock_attr_with_help(
- format!("unknown attribute `{x}`. Did you mean `{flag}`?"),
- help,
+ format!("unknown attribute `{x}`"),
+ |lint| {
+ lint.help(format!(
+ "there is an attribute with a similar name: `{flag}`"
+ ))
+ .help(help);
+ },
);
}
}
@@ -1370,7 +1381,7 @@ impl LangString {
};
if custom_code_classes_in_docs {
- call(&mut TagIterator::new(string, extra).into_iter())
+ call(&mut TagIterator::new(string, extra))
} else {
call(&mut tokens(string))
}
@@ -2000,6 +2011,7 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
map.insert("themeStyle".into(), 1);
map.insert("settings-menu".into(), 1);
map.insert("help-button".into(), 1);
+ map.insert("sidebar-button".into(), 1);
map.insert("main-content".into(), 1);
map.insert("toggle-all-docs".into(), 1);
map.insert("all-types".into(), 1);
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 50777134d..f0199703c 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -176,9 +176,9 @@ impl<'tcx> Context<'tcx> {
let mut render_redirect_pages = self.render_redirect_pages;
// If the item is stripped but inlined, links won't point to the item so no need to generate
// a file for it.
- if it.is_stripped() &&
- let Some(def_id) = it.def_id() &&
- def_id.is_local()
+ if it.is_stripped()
+ && let Some(def_id) = it.def_id()
+ && def_id.is_local()
{
if self.is_inside_inlined_module || self.shared.cache.inlined_items.contains(&def_id) {
// For now we're forced to generate a redirect page for stripped items until
@@ -371,7 +371,9 @@ impl<'tcx> Context<'tcx> {
path = href.into_inner().to_string_lossy().into_owned();
- if let Some(c) = path.as_bytes().last() && *c != b'/' {
+ if let Some(c) = path.as_bytes().last()
+ && *c != b'/'
+ {
path.push('/');
}
@@ -741,9 +743,10 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
shared.fs.write(scrape_examples_help_file, v)?;
}
- if let Some(ref redirections) = shared.redirections && !redirections.borrow().is_empty() {
- let redirect_map_path =
- self.dst.join(crate_name.as_str()).join("redirect-map.json");
+ if let Some(ref redirections) = shared.redirections
+ && !redirections.borrow().is_empty()
+ {
+ let redirect_map_path = self.dst.join(crate_name.as_str()).join("redirect-map.json");
let paths = serde_json::to_string(&*redirections.borrow()).unwrap();
shared.ensure_dir(&self.dst.join(crate_name.as_str()))?;
shared.fs.write(redirect_map_path, paths)?;
@@ -790,7 +793,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
}
}
if !self.is_inside_inlined_module {
- if let Some(def_id) = item.def_id() && self.cache().inlined_items.contains(&def_id) {
+ if let Some(def_id) = item.def_id()
+ && self.cache().inlined_items.contains(&def_id)
+ {
self.is_inside_inlined_module = true;
}
} else if !self.cache().document_hidden && item.is_doc_hidden() {
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index c52fa01bd..34350c2ed 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -45,7 +45,6 @@ use std::iter::Peekable;
use std::path::PathBuf;
use std::rc::Rc;
use std::str;
-use std::string::ToString;
use askama::Template;
use rustc_attr::{ConstStability, DeprecatedSince, Deprecation, StabilityLevel, StableSince};
@@ -66,7 +65,7 @@ use crate::clean::{self, ItemId, RenderedLink, SelfTy};
use crate::error::Error;
use crate::formats::cache::Cache;
use crate::formats::item_type::ItemType;
-use crate::formats::{AssocItemRender, Impl, RenderMode};
+use crate::formats::Impl;
use crate::html::escape::Escape;
use crate::html::format::{
display_fn, href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
@@ -89,6 +88,21 @@ pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ {
})
}
+/// Specifies whether rendering directly implemented trait items or ones from a certain Deref
+/// impl.
+pub(crate) enum AssocItemRender<'a> {
+ All,
+ DerefFor { trait_: &'a clean::Path, type_: &'a clean::Type, deref_mut_: bool },
+}
+
+/// For different handling of associated items from the Deref target of a type rather than the type
+/// itself.
+#[derive(Copy, Clone, PartialEq)]
+pub(crate) enum RenderMode {
+ Normal,
+ ForDeref { mut_: bool },
+}
+
// Helper structs for rendering items/sidebars and carrying along contextual
// information
@@ -113,6 +127,7 @@ pub(crate) struct IndexItem {
pub(crate) struct RenderType {
id: Option<RenderTypeId>,
generics: Option<Vec<RenderType>>,
+ bindings: Option<Vec<(RenderTypeId, Vec<RenderType>)>>,
}
impl Serialize for RenderType {
@@ -129,10 +144,15 @@ impl Serialize for RenderType {
Some(RenderTypeId::Index(idx)) => *idx,
_ => panic!("must convert render types to indexes before serializing"),
};
- if let Some(generics) = &self.generics {
+ if self.generics.is_some() || self.bindings.is_some() {
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element(&id)?;
- seq.serialize_element(generics)?;
+ seq.serialize_element(self.generics.as_ref().map(Vec::as_slice).unwrap_or_default())?;
+ if self.bindings.is_some() {
+ seq.serialize_element(
+ self.bindings.as_ref().map(Vec::as_slice).unwrap_or_default(),
+ )?;
+ }
seq.end()
} else {
id.serialize(serializer)
@@ -140,13 +160,31 @@ impl Serialize for RenderType {
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Copy, Debug)]
pub(crate) enum RenderTypeId {
DefId(DefId),
Primitive(clean::PrimitiveType),
+ AssociatedType(Symbol),
Index(isize),
}
+impl Serialize for RenderTypeId {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let id = match &self {
+ // 0 is a sentinel, everything else is one-indexed
+ // concrete type
+ RenderTypeId::Index(idx) if *idx >= 0 => idx + 1,
+ // generic type parameter
+ RenderTypeId::Index(idx) => *idx,
+ _ => panic!("must convert render types to indexes before serializing"),
+ };
+ id.serialize(serializer)
+ }
+}
+
/// Full type of functions/methods in the search index.
#[derive(Debug)]
pub(crate) struct IndexItemFunctionType {
@@ -171,16 +209,23 @@ impl Serialize for IndexItemFunctionType {
} else {
let mut seq = serializer.serialize_seq(None)?;
match &self.inputs[..] {
- [one] if one.generics.is_none() => seq.serialize_element(one)?,
+ [one] if one.generics.is_none() && one.bindings.is_none() => {
+ seq.serialize_element(one)?
+ }
_ => seq.serialize_element(&self.inputs)?,
}
match &self.output[..] {
[] if self.where_clause.is_empty() => {}
- [one] if one.generics.is_none() => seq.serialize_element(one)?,
+ [one] if one.generics.is_none() && one.bindings.is_none() => {
+ seq.serialize_element(one)?
+ }
_ => seq.serialize_element(&self.output)?,
}
for constraint in &self.where_clause {
- if let [one] = &constraint[..] && one.generics.is_none() {
+ if let [one] = &constraint[..]
+ && one.generics.is_none()
+ && one.bindings.is_none()
+ {
seq.serialize_element(one)?;
} else {
seq.serialize_element(constraint)?;
@@ -627,7 +672,7 @@ fn short_item_info(
format!("Deprecating in {version}")
}
}
- DeprecatedSince::Future => String::from("Deprecating in a future Rust version"),
+ DeprecatedSince::Future => String::from("Deprecating in a future version"),
DeprecatedSince::NonStandard(since) => {
format!("Deprecated since {}", Escape(since.as_str()))
}
@@ -915,7 +960,9 @@ fn render_stability_since_raw_with_extra(
containing_const_ver: Option<StableSince>,
extra_class: &str,
) -> bool {
- let stable_version = if ver != containing_ver && let Some(ver) = &ver {
+ let stable_version = if ver != containing_ver
+ && let Some(ver) = &ver
+ {
since_to_string(ver)
} else {
None
@@ -1097,7 +1144,7 @@ impl<'a> AssocItemLink<'a> {
fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) {
write!(
w,
- "<h2 id=\"{id}\" class=\"small-section-header\">\
+ "<h2 id=\"{id}\" class=\"section-header\">\
{title}\
<a href=\"#{id}\" class=\"anchor\">§</a>\
</h2>"
@@ -1348,8 +1395,7 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> O
if let Some(trait_) = &impl_.trait_ {
let trait_did = trait_.def_id();
- if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx()))
- {
+ if cx.cache().traits.get(&trait_did).is_some_and(|t| t.is_notable_trait(cx.tcx())) {
has_notable_trait = true;
}
}
@@ -1384,7 +1430,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
if let Some(trait_) = &impl_.trait_ {
let trait_did = trait_.def_id();
- if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx())) {
+ if cx.cache().traits.get(&trait_did).is_some_and(|t| t.is_notable_trait(cx.tcx())) {
if out.is_empty() {
write!(
&mut out,
@@ -1394,15 +1440,10 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
);
}
- //use the "where" class here to make it small
- write!(
- &mut out,
- "<span class=\"where fmt-newline\">{}</span>",
- impl_.print(false, cx)
- );
+ write!(&mut out, "<div class=\"where\">{}</div>", impl_.print(false, cx));
for it in &impl_.items {
if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
- out.push_str("<span class=\"where fmt-newline\"> ");
+ out.push_str("<div class=\"where\"> ");
let empty_set = FxHashSet::default();
let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set);
assoc_type(
@@ -1415,7 +1456,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
0,
cx,
);
- out.push_str(";</span>");
+ out.push_str(";</div>");
}
}
}
@@ -1901,7 +1942,7 @@ pub(crate) fn render_impl_summary(
if show_def_docs {
for it in &inner_impl.items {
if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
- w.write_str("<span class=\"where fmt-newline\"> ");
+ w.write_str("<div class=\"where\"> ");
assoc_type(
w,
it,
@@ -1912,7 +1953,7 @@ pub(crate) fn render_impl_summary(
0,
cx,
);
- w.write_str(";</span>");
+ w.write_str(";</div>");
}
}
}
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index d226701ba..5ca623f01 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1,5 +1,3 @@
-use clean::AttributesExt;
-
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
@@ -22,12 +20,13 @@ use super::{
item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
render_impl, render_rightside, render_stability_since_raw,
- render_stability_since_raw_with_extra, AssocItemLink, Context, ImplRenderingParameters,
+ render_stability_since_raw_with_extra, AssocItemLink, AssocItemRender, Context,
+ ImplRenderingParameters, RenderMode,
};
use crate::clean;
use crate::config::ModuleSorting;
use crate::formats::item_type::ItemType;
-use crate::formats::{AssocItemRender, Impl, RenderMode};
+use crate::formats::Impl;
use crate::html::escape::Escape;
use crate::html::format::{
display_fn, join_with_double_colon, print_abi_with_space, print_constness_with_space,
@@ -369,8 +368,8 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
if let (Some(a), Some(b)) = (s1, s2) {
match (a.is_stable(), b.is_stable()) {
(true, true) | (false, false) => {}
- (false, true) => return Ordering::Less,
- (true, false) => return Ordering::Greater,
+ (false, true) => return Ordering::Greater,
+ (true, false) => return Ordering::Less,
}
}
let lhs = i1.name.unwrap_or(kw::Empty);
@@ -429,7 +428,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
last_section = Some(my_section);
write!(
w,
- "<h2 id=\"{id}\" class=\"small-section-header\">\
+ "<h2 id=\"{id}\" class=\"section-header\">\
<a href=\"#{id}\">{name}</a>\
</h2>{ITEM_TABLE_OPEN}",
id = cx.derive_id(my_section.id()),
@@ -464,16 +463,9 @@ 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 = 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(tcx, &cx.cache().hidden_cfg),
- ..myitem.clone()
- };
+ let import_item =
+ clean::Item { item_id: import_def_id.into(), ..myitem.clone() };
let stab_tags = Some(extra_info_tags(&import_item, item, tcx).to_string());
stab_tags
@@ -596,8 +588,10 @@ fn extra_info_tags<'a, 'tcx: 'a>(
// The "rustc_private" crates are permanently unstable so it makes no sense
// to render "unstable" everywhere.
- if item.stability(tcx).as_ref().map(|s| s.is_unstable() && s.feature != sym::rustc_private)
- == Some(true)
+ if item
+ .stability(tcx)
+ .as_ref()
+ .is_some_and(|s| s.is_unstable() && s.feature != sym::rustc_private)
{
write!(f, "{}", tag_html("unstable", "", "Experimental"))?;
}
@@ -824,7 +818,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) {
write!(
w,
- "<h2 id=\"{0}\" class=\"small-section-header\">\
+ "<h2 id=\"{0}\" class=\"section-header\">\
{1}<a href=\"#{0}\" class=\"anchor\">§</a>\
</h2>{2}",
id, title, extra_content
@@ -974,8 +968,9 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
// if any Types with the same name but different DefId have been found.
let mut implementor_dups: FxHashMap<Symbol, (DefId, bool)> = FxHashMap::default();
for implementor in implementors {
- if let Some(did) = implementor.inner_impl().for_.without_borrowed_ref().def_id(cache) &&
- !did.is_local() {
+ if let Some(did) = implementor.inner_impl().for_.without_borrowed_ref().def_id(cache)
+ && !did.is_local()
+ {
extern_crates.insert(did.krate);
}
match implementor.inner_impl().for_.without_borrowed_ref() {
@@ -1152,9 +1147,10 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
.take(cx.current.len())
.chain(std::iter::once("trait.impl"))
.collect();
- if let Some(did) = it.item_id.as_def_id() &&
- let get_extern = { || cache.external_paths.get(&did).map(|s| &s.0) } &&
- let Some(fqp) = cache.exact_paths.get(&did).or_else(get_extern) {
+ if let Some(did) = it.item_id.as_def_id()
+ && let get_extern = { || cache.external_paths.get(&did).map(|s| &s.0) }
+ && let Some(fqp) = cache.exact_paths.get(&did).or_else(get_extern)
+ {
js_src_path.extend(fqp[..fqp.len() - 1].iter().copied());
js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), fqp.last().unwrap()));
} else {
@@ -1255,7 +1251,7 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c
if let Some(inner_type) = &t.inner_type {
write!(
w,
- "<h2 id=\"aliased-type\" class=\"small-section-header\">\
+ "<h2 id=\"aliased-type\" class=\"section-header\">\
Aliased Type<a href=\"#aliased-type\" class=\"anchor\">§</a></h2>"
);
@@ -1499,10 +1495,12 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>(
s: &'a [clean::Item],
) -> impl fmt::Display + 'a + Captures<'cx> {
display_fn(|f| {
- if s.iter()
- .all(|field| matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..))))
+ if !s.is_empty()
+ && s.iter().all(|field| {
+ matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
+ })
{
- return f.write_str("/* private fields */");
+ return f.write_str("<span class=\"comment\">/* private fields */</span>");
}
for (i, ty) in s.iter().enumerate() {
@@ -1564,8 +1562,8 @@ fn should_show_enum_discriminant(
) -> bool {
let mut has_variants_with_value = false;
for variant in variants {
- if let clean::VariantItem(ref var) = *variant.kind &&
- matches!(var.kind, clean::VariantKind::CLike)
+ if let clean::VariantItem(ref var) = *variant.kind
+ && matches!(var.kind, clean::VariantKind::CLike)
{
has_variants_with_value |= var.discriminant.is_some();
} else {
@@ -1659,7 +1657,7 @@ fn render_enum_fields(
}
if variants_stripped && !is_non_exhaustive {
- w.write_str(" // some variants omitted\n");
+ w.write_str(" <span class=\"comment\">// some variants omitted</span>\n");
}
if toggle {
toggle_close(&mut w);
@@ -1678,7 +1676,7 @@ fn item_variants(
let tcx = cx.tcx();
write!(
w,
- "<h2 id=\"variants\" class=\"variants small-section-header\">\
+ "<h2 id=\"variants\" class=\"variants section-header\">\
Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\
</h2>\
{}\
@@ -1706,8 +1704,8 @@ fn item_variants(
" rightside",
);
w.write_str("<h3 class=\"code-header\">");
- if let clean::VariantItem(ref var) = *variant.kind &&
- let clean::VariantKind::CLike = var.kind
+ if let clean::VariantItem(ref var) = *variant.kind
+ && let clean::VariantKind::CLike = var.kind
{
display_c_like_variant(
w,
@@ -1730,7 +1728,14 @@ fn item_variants(
w.write_str("</h3></section>");
let heading_and_fields = match &variant_data.kind {
- clean::VariantKind::Struct(s) => Some(("Fields", &s.fields)),
+ clean::VariantKind::Struct(s) => {
+ // If there is no field to display, no need to add the heading.
+ if s.fields.iter().any(|f| !f.is_doc_hidden()) {
+ Some(("Fields", &s.fields))
+ } else {
+ None
+ }
+ }
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.
@@ -1765,7 +1770,7 @@ fn item_variants(
write!(
w,
"<div class=\"sub-variant-field\">\
- <span id=\"{id}\" class=\"small-section-header\">\
+ <span id=\"{id}\" class=\"section-header\">\
<a href=\"#{id}\" class=\"anchor field\">§</a>\
<code>{f}: {t}</code>\
</span>",
@@ -1804,7 +1809,8 @@ fn item_proc_macro(
let name = it.name.expect("proc-macros always have names");
match m.kind {
MacroKind::Bang => {
- write!(buffer, "{name}!() {{ /* proc-macro */ }}").unwrap();
+ write!(buffer, "{name}!() {{ <span class=\"comment\">/* proc-macro */</span> }}")
+ .unwrap();
}
MacroKind::Attr => {
write!(buffer, "#[{name}]").unwrap();
@@ -1812,7 +1818,12 @@ fn item_proc_macro(
MacroKind::Derive => {
write!(buffer, "#[derive({name})]").unwrap();
if !m.helpers.is_empty() {
- buffer.write_str("\n{\n // Attributes available to this derive:\n").unwrap();
+ buffer
+ .write_str(
+ "\n{\n \
+ <span class=\"comment\">// Attributes available to this derive:</span>\n",
+ )
+ .unwrap();
for attr in &m.helpers {
writeln!(buffer, " #[{attr}]").unwrap();
}
@@ -1922,7 +1933,7 @@ fn item_fields(
if fields.peek().is_some() {
write!(
w,
- "<h2 id=\"fields\" class=\"fields small-section-header\">\
+ "<h2 id=\"fields\" class=\"fields section-header\">\
{}{}<a href=\"#fields\" class=\"anchor\">§</a>\
</h2>\
{}",
@@ -1936,7 +1947,7 @@ fn item_fields(
let id = cx.derive_id(format!("{typ}.{field_name}", typ = ItemType::StructField));
write!(
w,
- "<span id=\"{id}\" class=\"{item_type} small-section-header\">\
+ "<span id=\"{id}\" class=\"{item_type} section-header\">\
<a href=\"#{id}\" class=\"anchor field\">§</a>\
<code>{field_name}: {ty}</code>\
</span>",
@@ -2174,7 +2185,7 @@ fn render_union<'a, 'cx: 'a>(
}
if it.has_stripped_entries().unwrap() {
- write!(f, " /* private fields */\n")?;
+ write!(f, " <span class=\"comment\">/* private fields */</span>\n")?;
}
if toggle {
toggle_close(&mut f);
@@ -2260,11 +2271,11 @@ fn render_struct_fields(
if has_visible_fields {
if has_stripped_entries {
- write!(w, "\n{tab} /* private fields */");
+ write!(w, "\n{tab} <span class=\"comment\">/* private fields */</span>");
}
write!(w, "\n{tab}");
} else if has_stripped_entries {
- write!(w, " /* private fields */ ");
+ write!(w, " <span class=\"comment\">/* private fields */</span> ");
}
if toggle {
toggle_close(&mut w);
@@ -2273,10 +2284,12 @@ fn render_struct_fields(
}
Some(CtorKind::Fn) => {
w.write_str("(");
- if fields.iter().all(|field| {
- matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
- }) {
- write!(w, "/* private fields */");
+ if !fields.is_empty()
+ && fields.iter().all(|field| {
+ matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
+ })
+ {
+ write!(w, "<span class=\"comment\">/* private fields */</span>");
} else {
for (i, field) in fields.iter().enumerate() {
if i > 0 {
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index af1dab594..a1029320d 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -3,8 +3,10 @@ use std::collections::BTreeMap;
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_middle::ty::TyCtxt;
+use rustc_span::def_id::DefId;
use rustc_span::symbol::Symbol;
use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
+use thin_vec::ThinVec;
use crate::clean;
use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate};
@@ -22,6 +24,7 @@ pub(crate) fn build_index<'tcx>(
) -> String {
let mut itemid_to_pathid = FxHashMap::default();
let mut primitives = FxHashMap::default();
+ let mut associated_types = FxHashMap::default();
let mut crate_paths = vec![];
// Attach all orphan items to the type's definition if the type
@@ -38,7 +41,13 @@ pub(crate) fn build_index<'tcx>(
parent: Some(parent),
parent_idx: None,
impl_id,
- search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache),
+ search_type: get_function_type_for_search(
+ item,
+ tcx,
+ impl_generics.as_ref(),
+ Some(parent),
+ cache,
+ ),
aliases: item.attrs.get_doc_aliases(),
deprecation: item.deprecation(tcx),
});
@@ -76,83 +85,139 @@ pub(crate) fn build_index<'tcx>(
let mut search_index = std::mem::replace(&mut cache.search_index, Vec::new());
for item in search_index.iter_mut() {
fn insert_into_map<F: std::hash::Hash + Eq>(
- ty: &mut RenderType,
map: &mut FxHashMap<F, isize>,
itemid: F,
lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
item_type: ItemType,
path: &[Symbol],
- ) {
+ ) -> RenderTypeId {
match map.entry(itemid) {
- Entry::Occupied(entry) => ty.id = Some(RenderTypeId::Index(*entry.get())),
+ Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
Entry::Vacant(entry) => {
let pathid = *lastpathid;
entry.insert(pathid);
*lastpathid += 1;
crate_paths.push((item_type, path.to_vec()));
- ty.id = Some(RenderTypeId::Index(pathid));
+ RenderTypeId::Index(pathid)
}
}
}
- fn convert_render_type(
- ty: &mut RenderType,
+ fn convert_render_type_id(
+ id: RenderTypeId,
cache: &mut Cache,
itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
primitives: &mut FxHashMap<Symbol, isize>,
+ associated_types: &mut FxHashMap<Symbol, isize>,
lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
- ) {
- if let Some(generics) = &mut ty.generics {
- for item in generics {
- convert_render_type(
- item,
- cache,
- itemid_to_pathid,
- primitives,
- lastpathid,
- crate_paths,
- );
- }
- }
+ ) -> Option<RenderTypeId> {
let Cache { ref paths, ref external_paths, .. } = *cache;
- let Some(id) = ty.id.clone() else {
- assert!(ty.generics.is_some());
- return;
- };
match id {
RenderTypeId::DefId(defid) => {
if let Some(&(ref fqp, item_type)) =
paths.get(&defid).or_else(|| external_paths.get(&defid))
{
- insert_into_map(
- ty,
+ Some(insert_into_map(
itemid_to_pathid,
ItemId::DefId(defid),
lastpathid,
crate_paths,
item_type,
fqp,
- );
+ ))
} else {
- ty.id = None;
+ None
}
}
RenderTypeId::Primitive(primitive) => {
let sym = primitive.as_sym();
- insert_into_map(
- ty,
+ Some(insert_into_map(
primitives,
sym,
lastpathid,
crate_paths,
ItemType::Primitive,
&[sym],
+ ))
+ }
+ RenderTypeId::Index(_) => Some(id),
+ RenderTypeId::AssociatedType(sym) => Some(insert_into_map(
+ associated_types,
+ sym,
+ lastpathid,
+ crate_paths,
+ ItemType::AssocType,
+ &[sym],
+ )),
+ }
+ }
+
+ fn convert_render_type(
+ ty: &mut RenderType,
+ cache: &mut Cache,
+ itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
+ primitives: &mut FxHashMap<Symbol, isize>,
+ associated_types: &mut FxHashMap<Symbol, isize>,
+ lastpathid: &mut isize,
+ crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
+ ) {
+ if let Some(generics) = &mut ty.generics {
+ for item in generics {
+ convert_render_type(
+ item,
+ cache,
+ itemid_to_pathid,
+ primitives,
+ associated_types,
+ lastpathid,
+ crate_paths,
);
}
- RenderTypeId::Index(_) => {}
}
+ if let Some(bindings) = &mut ty.bindings {
+ bindings.retain_mut(|(associated_type, constraints)| {
+ let converted_associated_type = convert_render_type_id(
+ *associated_type,
+ cache,
+ itemid_to_pathid,
+ primitives,
+ associated_types,
+ lastpathid,
+ crate_paths,
+ );
+ let Some(converted_associated_type) = converted_associated_type else {
+ return false;
+ };
+ *associated_type = converted_associated_type;
+ for constraint in constraints {
+ convert_render_type(
+ constraint,
+ cache,
+ itemid_to_pathid,
+ primitives,
+ associated_types,
+ lastpathid,
+ crate_paths,
+ );
+ }
+ true
+ });
+ }
+ let Some(id) = ty.id.clone() else {
+ assert!(ty.generics.is_some());
+ return;
+ };
+ ty.id = convert_render_type_id(
+ id,
+ cache,
+ itemid_to_pathid,
+ primitives,
+ associated_types,
+ lastpathid,
+ crate_paths,
+ );
}
if let Some(search_type) = &mut item.search_type {
for item in &mut search_type.inputs {
@@ -161,6 +226,7 @@ pub(crate) fn build_index<'tcx>(
cache,
&mut itemid_to_pathid,
&mut primitives,
+ &mut associated_types,
&mut lastpathid,
&mut crate_paths,
);
@@ -171,6 +237,7 @@ pub(crate) fn build_index<'tcx>(
cache,
&mut itemid_to_pathid,
&mut primitives,
+ &mut associated_types,
&mut lastpathid,
&mut crate_paths,
);
@@ -182,6 +249,7 @@ pub(crate) fn build_index<'tcx>(
cache,
&mut itemid_to_pathid,
&mut primitives,
+ &mut associated_types,
&mut lastpathid,
&mut crate_paths,
);
@@ -228,10 +296,11 @@ pub(crate) fn build_index<'tcx>(
let mut associated_item_duplicates = FxHashMap::<(isize, ItemType, Symbol), usize>::default();
for &item in &crate_items {
- if item.impl_id.is_some() && let Some(parent_idx) = item.parent_idx {
- let count = associated_item_duplicates
- .entry((parent_idx, item.ty, item.name))
- .or_insert(0);
+ if item.impl_id.is_some()
+ && let Some(parent_idx) = item.parent_idx
+ {
+ let count =
+ associated_item_duplicates.entry((parent_idx, item.ty, item.name)).or_insert(0);
*count += 1;
}
}
@@ -419,7 +488,7 @@ pub(crate) fn build_index<'tcx>(
// Collect the index into a string
format!(
- r#""{}":{}"#,
+ r#"["{}",{}]"#,
krate.name(tcx),
serde_json::to_string(&CrateData {
doc: crate_doc,
@@ -441,12 +510,39 @@ pub(crate) fn get_function_type_for_search<'tcx>(
item: &clean::Item,
tcx: TyCtxt<'tcx>,
impl_generics: Option<&(clean::Type, clean::Generics)>,
+ parent: Option<DefId>,
cache: &Cache,
) -> Option<IndexItemFunctionType> {
+ let mut trait_info = None;
+ let impl_or_trait_generics = impl_generics.or_else(|| {
+ if let Some(def_id) = parent
+ && let Some(trait_) = cache.traits.get(&def_id)
+ && let Some((path, _)) =
+ cache.paths.get(&def_id).or_else(|| cache.external_paths.get(&def_id))
+ {
+ let path = clean::Path {
+ res: rustc_hir::def::Res::Def(rustc_hir::def::DefKind::Trait, def_id),
+ segments: path
+ .iter()
+ .map(|name| clean::PathSegment {
+ name: *name,
+ args: clean::GenericArgs::AngleBracketed {
+ args: Vec::new().into_boxed_slice(),
+ bindings: ThinVec::new(),
+ },
+ })
+ .collect(),
+ };
+ trait_info = Some((clean::Type::Path { path }, trait_.generics.clone()));
+ Some(trait_info.as_ref().unwrap())
+ } else {
+ None
+ }
+ });
let (mut inputs, mut output, where_clause) = match *item.kind {
- clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, impl_generics, cache),
- clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
- clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
+ clean::FunctionItem(ref f) | clean::MethodItem(ref f, _) | clean::TyMethodItem(ref f) => {
+ get_fn_inputs_and_outputs(f, tcx, impl_or_trait_generics, cache)
+ }
_ => return None,
};
@@ -456,14 +552,23 @@ pub(crate) fn get_function_type_for_search<'tcx>(
Some(IndexItemFunctionType { inputs, output, where_clause })
}
-fn get_index_type(clean_type: &clean::Type, generics: Vec<RenderType>) -> RenderType {
+fn get_index_type(
+ clean_type: &clean::Type,
+ generics: Vec<RenderType>,
+ rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
+) -> RenderType {
RenderType {
- id: get_index_type_id(clean_type),
+ id: get_index_type_id(clean_type, rgen),
generics: if generics.is_empty() { None } else { Some(generics) },
+ bindings: None,
}
}
-fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
+fn get_index_type_id(
+ clean_type: &clean::Type,
+ rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
+) -> Option<RenderTypeId> {
+ use rustc_hir::def::{DefKind, Res};
match *clean_type {
clean::Type::Path { ref path, .. } => Some(RenderTypeId::DefId(path.def_id())),
clean::DynTrait(ref bounds, _) => {
@@ -471,18 +576,27 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
}
clean::Primitive(p) => Some(RenderTypeId::Primitive(p)),
clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => {
- get_index_type_id(type_)
+ get_index_type_id(type_, rgen)
}
// The type parameters are converted to generics in `simplify_fn_type`
clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)),
clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)),
clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)),
+ clean::QPath(ref data) => {
+ if data.self_type.is_self_type()
+ && let Some(clean::Path { res: Res::Def(DefKind::Trait, trait_), .. }) = data.trait_
+ {
+ let idx = -isize::try_from(rgen.len() + 1).unwrap();
+ let (idx, _) = rgen
+ .entry(SimplifiedParam::AssociatedType(trait_, data.assoc.name))
+ .or_insert_with(|| (idx, Vec::new()));
+ Some(RenderTypeId::Index(*idx))
+ } else {
+ None
+ }
+ }
// Not supported yet
- clean::BareFunction(_)
- | clean::Generic(_)
- | clean::ImplTrait(_)
- | clean::QPath { .. }
- | clean::Infer => None,
+ clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
}
}
@@ -492,6 +606,9 @@ enum SimplifiedParam {
Symbol(Symbol),
// every argument-position impl trait is its own type parameter
Anonymous(isize),
+ // in a trait definition, the associated types are all bound to
+ // their own type parameter
+ AssociatedType(DefId, Symbol),
}
/// The point of this function is to lower generics and types into the simplified form that the
@@ -522,10 +639,17 @@ fn simplify_fn_type<'tcx, 'a>(
}
// First, check if it's "Self".
+ let mut is_self = false;
let mut arg = if let Some(self_) = self_ {
match &*arg {
- Type::BorrowedRef { type_, .. } if type_.is_self_type() => self_,
- type_ if type_.is_self_type() => self_,
+ Type::BorrowedRef { type_, .. } if type_.is_self_type() => {
+ is_self = true;
+ self_
+ }
+ type_ if type_.is_self_type() => {
+ is_self = true;
+ self_
+ }
arg => arg,
}
} else {
@@ -584,11 +708,19 @@ fn simplify_fn_type<'tcx, 'a>(
}
}
if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) {
- res.push(RenderType { id: Some(RenderTypeId::Index(*idx)), generics: None });
+ res.push(RenderType {
+ id: Some(RenderTypeId::Index(*idx)),
+ generics: None,
+ bindings: None,
+ });
} else {
let idx = -isize::try_from(rgen.len() + 1).unwrap();
rgen.insert(SimplifiedParam::Symbol(arg_s), (idx, type_bounds));
- res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None });
+ res.push(RenderType {
+ id: Some(RenderTypeId::Index(idx)),
+ generics: None,
+ bindings: None,
+ });
}
} else if let Type::ImplTrait(ref bounds) = *arg {
let mut type_bounds = Vec::new();
@@ -610,12 +742,16 @@ fn simplify_fn_type<'tcx, 'a>(
}
if is_return && !type_bounds.is_empty() {
// In parameter position, `impl Trait` is a unique thing.
- res.push(RenderType { id: None, generics: Some(type_bounds) });
+ res.push(RenderType { id: None, generics: Some(type_bounds), bindings: None });
} else {
// In parameter position, `impl Trait` is the same as an unnamed generic parameter.
let idx = -isize::try_from(rgen.len() + 1).unwrap();
rgen.insert(SimplifiedParam::Anonymous(idx), (idx, type_bounds));
- res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None });
+ res.push(RenderType {
+ id: Some(RenderTypeId::Index(idx)),
+ generics: None,
+ bindings: None,
+ });
}
} else if let Type::Slice(ref ty) = *arg {
let mut ty_generics = Vec::new();
@@ -630,7 +766,7 @@ fn simplify_fn_type<'tcx, 'a>(
is_return,
cache,
);
- res.push(get_index_type(arg, ty_generics));
+ res.push(get_index_type(arg, ty_generics, rgen));
} else if let Type::Array(ref ty, _) = *arg {
let mut ty_generics = Vec::new();
simplify_fn_type(
@@ -644,7 +780,7 @@ fn simplify_fn_type<'tcx, 'a>(
is_return,
cache,
);
- res.push(get_index_type(arg, ty_generics));
+ res.push(get_index_type(arg, ty_generics, rgen));
} else if let Type::Tuple(ref tys) = *arg {
let mut ty_generics = Vec::new();
for ty in tys {
@@ -660,7 +796,7 @@ fn simplify_fn_type<'tcx, 'a>(
cache,
);
}
- res.push(get_index_type(arg, ty_generics));
+ res.push(get_index_type(arg, ty_generics, rgen));
} 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.
@@ -668,12 +804,16 @@ fn simplify_fn_type<'tcx, 'a>(
// So in here, we can add it directly and look for its own type parameters (so for `Option`,
// we will look for them but not for `T`).
let mut ty_generics = Vec::new();
- if let Some(arg_generics) = arg.generics() {
- for gen in arg_generics.iter() {
+ let mut ty_bindings = Vec::new();
+ if let Some(arg_generics) = arg.generic_args() {
+ for ty in arg_generics.into_iter().filter_map(|gen| match gen {
+ clean::GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ }) {
simplify_fn_type(
self_,
generics,
- gen,
+ &ty,
tcx,
recurse + 1,
&mut ty_generics,
@@ -682,17 +822,180 @@ fn simplify_fn_type<'tcx, 'a>(
cache,
);
}
+ for binding in arg_generics.bindings() {
+ simplify_fn_binding(
+ self_,
+ generics,
+ &binding,
+ tcx,
+ recurse + 1,
+ &mut ty_bindings,
+ rgen,
+ is_return,
+ cache,
+ );
+ }
+ }
+ // Every trait associated type on self gets assigned to a type parameter index
+ // this same one is used later for any appearances of these types
+ //
+ // for example, Iterator::next is:
+ //
+ // trait Iterator {
+ // fn next(&mut self) -> Option<Self::Item>
+ // }
+ //
+ // Self is technically just Iterator, but we want to pretend it's more like this:
+ //
+ // fn next<T>(self: Iterator<Item=T>) -> Option<T>
+ if is_self
+ && let Type::Path { path } = arg
+ && let def_id = path.def_id()
+ && let Some(trait_) = cache.traits.get(&def_id)
+ && trait_.items.iter().any(|at| at.is_ty_associated_type())
+ {
+ for assoc_ty in &trait_.items {
+ if let clean::ItemKind::TyAssocTypeItem(_generics, bounds) = &*assoc_ty.kind
+ && let Some(name) = assoc_ty.name
+ {
+ let idx = -isize::try_from(rgen.len() + 1).unwrap();
+ let (idx, stored_bounds) = rgen
+ .entry(SimplifiedParam::AssociatedType(def_id, name))
+ .or_insert_with(|| (idx, Vec::new()));
+ let idx = *idx;
+ if stored_bounds.is_empty() {
+ // Can't just pass stored_bounds to simplify_fn_type,
+ // because it also accepts rgen as a parameter.
+ // Instead, have it fill in this local, then copy it into the map afterward.
+ let mut type_bounds = Vec::new();
+ for bound in bounds {
+ if let Some(path) = bound.get_trait_path() {
+ let ty = Type::Path { path };
+ simplify_fn_type(
+ self_,
+ generics,
+ &ty,
+ tcx,
+ recurse + 1,
+ &mut type_bounds,
+ rgen,
+ is_return,
+ cache,
+ );
+ }
+ }
+ let stored_bounds = &mut rgen
+ .get_mut(&SimplifiedParam::AssociatedType(def_id, name))
+ .unwrap()
+ .1;
+ if stored_bounds.is_empty() {
+ *stored_bounds = type_bounds;
+ }
+ }
+ ty_bindings.push((
+ RenderTypeId::AssociatedType(name),
+ vec![RenderType {
+ id: Some(RenderTypeId::Index(idx)),
+ generics: None,
+ bindings: None,
+ }],
+ ))
+ }
+ }
}
- let id = get_index_type_id(&arg);
+ let id = get_index_type_id(&arg, rgen);
if id.is_some() || !ty_generics.is_empty() {
res.push(RenderType {
id,
+ bindings: if ty_bindings.is_empty() { None } else { Some(ty_bindings) },
generics: if ty_generics.is_empty() { None } else { Some(ty_generics) },
});
}
}
}
+fn simplify_fn_binding<'tcx, 'a>(
+ self_: Option<&'a Type>,
+ generics: &Generics,
+ binding: &'a clean::TypeBinding,
+ tcx: TyCtxt<'tcx>,
+ recurse: usize,
+ res: &mut Vec<(RenderTypeId, Vec<RenderType>)>,
+ rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
+ is_return: bool,
+ cache: &Cache,
+) {
+ let mut ty_binding_constraints = Vec::new();
+ let ty_binding_assoc = RenderTypeId::AssociatedType(binding.assoc.name);
+ for gen in &binding.assoc.args {
+ match gen {
+ clean::GenericArg::Type(arg) => simplify_fn_type(
+ self_,
+ generics,
+ &arg,
+ tcx,
+ recurse + 1,
+ &mut ty_binding_constraints,
+ rgen,
+ is_return,
+ cache,
+ ),
+ clean::GenericArg::Lifetime(_)
+ | clean::GenericArg::Const(_)
+ | clean::GenericArg::Infer => {}
+ }
+ }
+ for binding in binding.assoc.args.bindings() {
+ simplify_fn_binding(
+ self_,
+ generics,
+ &binding,
+ tcx,
+ recurse + 1,
+ res,
+ rgen,
+ is_return,
+ cache,
+ );
+ }
+ match &binding.kind {
+ clean::TypeBindingKind::Equality { term } => {
+ if let clean::Term::Type(arg) = &term {
+ simplify_fn_type(
+ self_,
+ generics,
+ arg,
+ tcx,
+ recurse + 1,
+ &mut ty_binding_constraints,
+ rgen,
+ is_return,
+ cache,
+ );
+ }
+ }
+ clean::TypeBindingKind::Constraint { bounds } => {
+ for bound in &bounds[..] {
+ if let Some(path) = bound.get_trait_path() {
+ let ty = Type::Path { path };
+ simplify_fn_type(
+ self_,
+ generics,
+ &ty,
+ tcx,
+ recurse + 1,
+ &mut ty_binding_constraints,
+ rgen,
+ is_return,
+ cache,
+ );
+ }
+ }
+ }
+ }
+ res.push((ty_binding_assoc, ty_binding_constraints));
+}
+
/// Return the full list of types when bounds have been resolved.
///
/// i.e. `fn foo<A: Display, B: Option<A>>(x: u32, y: B)` will return
@@ -700,13 +1003,15 @@ fn simplify_fn_type<'tcx, 'a>(
fn get_fn_inputs_and_outputs<'tcx>(
func: &Function,
tcx: TyCtxt<'tcx>,
- impl_generics: Option<&(clean::Type, clean::Generics)>,
+ impl_or_trait_generics: Option<&(clean::Type, clean::Generics)>,
cache: &Cache,
) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) {
let decl = &func.decl;
+ let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
+
let combined_generics;
- let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_generics {
+ let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_or_trait_generics {
match (impl_generics.is_empty(), func.generics.is_empty()) {
(true, _) => (Some(impl_self), &func.generics),
(_, true) => (Some(impl_self), impl_generics),
@@ -728,8 +1033,6 @@ fn get_fn_inputs_and_outputs<'tcx>(
(None, &func.generics)
};
- let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
-
let mut arg_types = Vec::new();
for arg in decl.inputs.values.iter() {
simplify_fn_type(
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index ba4aaaff5..3d28937eb 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -435,9 +435,9 @@ fn sidebar_deref_methods<'a>(
}
// Recurse into any further impls that might exist for `target`
- if let Some(target_did) = target.def_id(c) &&
- let Some(target_impls) = c.impls.get(&target_did) &&
- let Some(target_deref_impl) = target_impls.iter().find(|i| {
+ if let Some(target_did) = target.def_id(c)
+ && let Some(target_impls) = c.impls.get(&target_did)
+ && let Some(target_deref_impl) = target_impls.iter().find(|i| {
i.inner_impl()
.trait_
.as_ref()
@@ -445,14 +445,7 @@ fn sidebar_deref_methods<'a>(
.unwrap_or(false)
})
{
- sidebar_deref_methods(
- cx,
- out,
- target_deref_impl,
- target_impls,
- derefs,
- used_links,
- );
+ sidebar_deref_methods(cx, out, target_deref_impl, target_impls, derefs, used_links);
}
}
}
@@ -494,8 +487,13 @@ fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> {
&& it
.name
.or_else(|| {
- if let clean::ImportItem(ref i) = *it.kind &&
- let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None }
+ if let clean::ImportItem(ref i) = *it.kind
+ && let clean::ImportKind::Simple(s) = i.kind
+ {
+ Some(s)
+ } else {
+ None
+ }
})
.is_some()
})
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
index 5f130f187..d1ece7337 100644
--- a/src/librustdoc/html/render/span_map.rs
+++ b/src/librustdoc/html/render/span_map.rs
@@ -94,7 +94,7 @@ impl<'tcx> SpanMapVisitor<'tcx> {
/// Used to generate links on items' definition to go to their documentation page.
pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) {
- if let Some(Node::Item(item)) = self.tcx.hir().find(hir_id) {
+ if let Some(Node::Item(item)) = self.tcx.opt_hir_node(hir_id) {
if let Some(span) = self.tcx.def_ident_span(item.owner_id) {
let cspan = clean::Span::new(span);
// If the span isn't from the current crate, we ignore it.
@@ -177,7 +177,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
if !span.overlaps(m.spans.inner_span) {
// Now that we confirmed it's a file import, we want to get the span for the module
// name only and not all the "mod foo;".
- if let Some(Node::Item(item)) = self.tcx.hir().find(id) {
+ if let Some(Node::Item(item)) = self.tcx.opt_hir_node(id) {
self.matches.insert(
item.ident.span,
LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)),
diff --git a/src/librustdoc/html/render/type_layout.rs b/src/librustdoc/html/render/type_layout.rs
index 377daaeb9..738ea0aee 100644
--- a/src/librustdoc/html/render/type_layout.rs
+++ b/src/librustdoc/html/render/type_layout.rs
@@ -42,37 +42,35 @@ pub(crate) fn document_type_layout<'a, 'cx: 'a>(
let ty = tcx.type_of(ty_def_id).instantiate_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()
+ 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 {
- Vec::new()
+ 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();
@@ -81,6 +79,7 @@ pub(crate) fn document_type_layout<'a, 'cx: 'a>(
TypeLayoutSize { is_unsized, is_uninhabited, size }
});
- Ok(TypeLayout { variants, type_layout_size }.render_into(f).unwrap())
+ TypeLayout { variants, type_layout_size }.render_into(f).unwrap();
+ Ok(())
})
}
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index d2c7c578c..6408e97df 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -15,14 +15,14 @@ use rustc_span::Symbol;
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
-use super::{collect_paths_for_type, ensure_trailing_slash, Context};
+use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode};
use crate::clean::{Crate, Item, ItemId, ItemKind};
use crate::config::{EmitType, RenderOptions};
use crate::docfs::PathError;
use crate::error::Error;
use crate::formats::cache::Cache;
use crate::formats::item_type::ItemType;
-use crate::formats::{Impl, RenderMode};
+use crate::formats::Impl;
use crate::html::format::Buffer;
use crate::html::render::{AssocItemLink, ImplRenderingParameters};
use crate::html::{layout, static_files};
@@ -167,23 +167,24 @@ pub(super) fn write_shared(
let mut krates = Vec::new();
if path.exists() {
- let prefix = format!("\"{krate}\"");
+ let prefix = format!("[\"{krate}\"");
for line in BufReader::new(File::open(path)?).lines() {
let line = line?;
- if !line.starts_with('"') {
+ if !line.starts_with("[\"") {
continue;
}
if line.starts_with(&prefix) {
continue;
}
- if line.ends_with(",\\") {
+ if line.ends_with("],\\") {
ret.push(line[..line.len() - 2].to_string());
} else {
// Ends with "\\" (it's the case for the last added crate line)
ret.push(line[..line.len() - 1].to_string());
}
krates.push(
- line.split('"')
+ line[1..] // We skip the `[` parent at the beginning of the line.
+ .split('"')
.find(|s| !s.is_empty())
.map(|s| s.to_owned())
.unwrap_or_else(String::new),
@@ -285,7 +286,7 @@ pub(super) fn write_shared(
let (mut all_sources, _krates) =
try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst);
all_sources.push(format!(
- r#""{}":{}"#,
+ r#"["{}",{}]"#,
&krate.name(cx.tcx()),
hierarchy
.to_json_string()
@@ -296,9 +297,12 @@ pub(super) fn write_shared(
.replace("\\\"", "\\\\\"")
));
all_sources.sort();
- let mut v = String::from("var srcIndex = JSON.parse('{\\\n");
+ // This needs to be `var`, not `const`.
+ // This variable needs declared in the current global scope so that if
+ // src-script.js loads first, it can pick it up.
+ let mut v = String::from("var srcIndex = new Map(JSON.parse('[\\\n");
v.push_str(&all_sources.join(",\\\n"));
- v.push_str("\\\n}');\ncreateSrcSidebar();\n");
+ v.push_str("\\\n]'));\ncreateSrcSidebar();\n");
Ok(v.into_bytes())
};
write_invocation_specific("src-files.js", &make_sources)?;
@@ -316,13 +320,16 @@ pub(super) fn write_shared(
// with rustdoc running in parallel.
all_indexes.sort();
write_invocation_specific("search-index.js", &|| {
- let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
+ // This needs to be `var`, not `const`.
+ // This variable needs declared in the current global scope so that if
+ // search.js loads first, it can pick it up.
+ let mut v = String::from("var searchIndex = new Map(JSON.parse('[\\\n");
v.push_str(&all_indexes.join(",\\\n"));
v.push_str(
r#"\
-}');
-if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)};
-if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
+]'));
+if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
+else if (window.initSearch) window.initSearch(searchIndex);
"#,
);
Ok(v.into_bytes())
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index ce620c226..e160ec12f 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -90,7 +90,9 @@ impl LocalSourcesCollector<'_, '_> {
);
let mut href = href.into_inner().to_string_lossy().into_owned();
- if let Some(c) = href.as_bytes().last() && *c != b'/' {
+ if let Some(c) = href.as_bytes().last()
+ && *c != b'/'
+ {
href.push('/');
}
let mut src_fname = p.file_name().expect("source has no filename").to_os_string();
@@ -212,7 +214,9 @@ impl SourceCollector<'_, '_> {
let root_path = PathBuf::from("../../").join(root_path.into_inner());
let mut root_path = root_path.to_string_lossy();
- if let Some(c) = root_path.as_bytes().last() && *c != b'/' {
+ if let Some(c) = root_path.as_bytes().last()
+ && *c != b'/'
+ {
root_path += "/";
}
let mut cur = self.dst.join(cur.into_inner());
diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css
index fe0cf6dc8..390e81277 100644
--- a/src/librustdoc/html/static/css/noscript.css
+++ b/src/librustdoc/html/static/css/noscript.css
@@ -9,7 +9,7 @@ rules.
margin-left: 0 !important;
}
-#copy-path {
+#copy-path, #sidebar-button, .sidebar-resizer {
/* It requires JS to work so no need to display it in this case. */
display: none;
}
@@ -132,6 +132,8 @@ nav.sub {
--scrape-example-help-hover-color: #000;
--scrape-example-code-wrapper-background-start: rgba(255, 255, 255, 1);
--scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0);
+ --sidebar-resizer-hover: hsl(207, 90%, 66%);
+ --sidebar-resizer-active: hsl(207, 90%, 54%);
}
/* End theme: light */
@@ -238,6 +240,8 @@ nav.sub {
--scrape-example-help-hover-color: #fff;
--scrape-example-code-wrapper-background-start: rgba(53, 53, 53, 1);
--scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0);
+ --sidebar-resizer-hover: hsl(207, 30%, 54%);
+ --sidebar-resizer-active: hsl(207, 90%, 54%);
}
/* End theme: dark */
}
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 9efdcd601..c4e97ded1 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -9,6 +9,12 @@
:root {
--nav-sub-mobile-padding: 8px;
--search-typename-width: 6.75rem;
+ /* DEFAULT_SIDEBAR_WIDTH
+ see main.js for information on these values
+ and on the RUSTDOC_MOBILE_BREAKPOINT */
+ --desktop-sidebar-width: 200px;
+ --src-sidebar-width: 300px;
+ --desktop-sidebar-z-index: 100;
}
/* See FiraSans-LICENSE.txt for the Fira Sans license. */
@@ -205,7 +211,7 @@ ul.all-items {
#toggle-all-docs,
a.anchor,
-.small-section-header a,
+.section-header a,
#src-sidebar a,
.rust a,
.sidebar h2 a,
@@ -383,13 +389,15 @@ img {
.sidebar {
font-size: 0.875rem;
- flex: 0 0 200px;
+ flex: 0 0 var(--desktop-sidebar-width);
+ width: var(--desktop-sidebar-width);
overflow-y: scroll;
overscroll-behavior: contain;
position: sticky;
height: 100vh;
top: 0;
left: 0;
+ z-index: var(--desktop-sidebar-z-index);
}
.rustdoc.src .sidebar {
@@ -398,7 +406,94 @@ img {
overflow-x: hidden;
/* The sidebar is by default hidden */
overflow-y: hidden;
- z-index: 1;
+}
+
+.hide-sidebar .sidebar,
+.hide-sidebar .sidebar-resizer {
+ display: none;
+}
+
+.sidebar-resizer {
+ touch-action: none;
+ width: 9px;
+ cursor: col-resize;
+ z-index: calc(var(--desktop-sidebar-z-index) + 1);
+ position: fixed;
+ height: 100%;
+ /* make sure there's a 1px gap between the scrollbar and resize handle */
+ left: calc(var(--desktop-sidebar-width) + 1px);
+}
+
+.rustdoc.src .sidebar-resizer {
+ /* when closed, place resizer glow on top of the normal src sidebar border (no need to worry
+ about sidebar) */
+ left: 49px;
+}
+
+.src-sidebar-expanded .rustdoc.src .sidebar-resizer {
+ /* for src sidebar, gap is already provided by 1px border on sidebar itself, so place resizer
+ to right of it */
+ left: var(--src-sidebar-width);
+}
+
+.sidebar-resizing {
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.sidebar-resizing * {
+ cursor: col-resize !important;
+}
+
+.sidebar-resizing .sidebar {
+ position: fixed;
+}
+.sidebar-resizing > body {
+ padding-left: var(--resizing-sidebar-width);
+}
+
+.sidebar-resizer:hover,
+.sidebar-resizer:active,
+.sidebar-resizer:focus,
+.sidebar-resizer.active {
+ width: 10px;
+ margin: 0;
+ /* when active or hovered, place resizer glow on top of the sidebar (right next to, or even
+ on top of, the scrollbar) */
+ left: var(--desktop-sidebar-width);
+ border-left: solid 1px var(--sidebar-resizer-hover);
+}
+
+.src-sidebar-expanded .rustdoc.src .sidebar-resizer:hover,
+.src-sidebar-expanded .rustdoc.src .sidebar-resizer:active,
+.src-sidebar-expanded .rustdoc.src .sidebar-resizer:focus,
+.src-sidebar-expanded .rustdoc.src .sidebar-resizer.active {
+ /* when active or hovered, place resizer glow on top of the normal src sidebar border */
+ left: calc(var(--src-sidebar-width) - 1px);
+}
+
+@media (pointer: coarse) {
+ .sidebar-resizer {
+ /* too easy to hit the resizer while trying to hit the [-] toggle */
+ display: none !important;
+ }
+}
+
+.sidebar-resizer.active {
+ /* make the resize tool bigger when actually resizing, to avoid :hover styles on other stuff
+ while resizing */
+ padding: 0 140px;
+ width: 2px;
+ margin-left: -140px;
+ border-left: none;
+}
+.sidebar-resizer.active:before {
+ border-left: solid 2px var(--sidebar-resizer-active);
+ display: block;
+ height: 100%;
+ content: "";
}
.sidebar, .mobile-topbar, .sidebar-menu-toggle,
@@ -416,7 +511,8 @@ img {
.src-sidebar-expanded .src .sidebar {
overflow-y: auto;
- flex-basis: 300px;
+ flex-basis: var(--src-sidebar-width);
+ width: var(--src-sidebar-width);
}
.src-sidebar-expanded .src .sidebar > *:not(#src-sidebar-toggle) {
@@ -477,6 +573,7 @@ ul.block, .block li {
display: block;
padding: 0.25rem; /* 4px */
margin-left: -0.25rem;
+ margin-right: 0.25rem;
}
.sidebar h2 {
@@ -703,11 +800,8 @@ pre, .rustdoc.src .example-wrap {
background: var(--table-alt-row-background-color);
}
-/* Shift "where ..." part of method or fn definition down a line */
-.method .where,
-.fn .where,
-.where.fmt-newline {
- display: block;
+/* "where ..." clauses with block display are also smaller */
+div.where {
white-space: pre-wrap;
font-size: 0.875rem;
}
@@ -742,13 +836,13 @@ nav.sub {
margin: 0 0 15px 0;
}
-.small-section-header {
+.section-header {
/* fields use <span> tags, but should get their own lines */
display: block;
position: relative;
}
-.small-section-header:hover > .anchor, .impl:hover > .anchor,
+.section-header:hover > .anchor, .impl:hover > .anchor,
.trait-impl:hover > .anchor, .variant:hover > .anchor {
display: initial;
}
@@ -761,11 +855,11 @@ nav.sub {
.anchor.field {
left: -5px;
}
-.small-section-header > .anchor {
+.section-header > .anchor {
left: -15px;
padding-right: 8px;
}
-h2.small-section-header > .anchor {
+h2.section-header > .anchor {
padding-right: 6px;
}
@@ -778,7 +872,7 @@ h2.small-section-header > .anchor {
text-decoration: underline;
}
-.crate.block a.current { font-weight: 500; }
+.crate.block li.current a { font-weight: 500; }
/* In most contexts we use `overflow-wrap: anywhere` to ensure that we can wrap
as much as needed on mobile (see
@@ -965,7 +1059,7 @@ so that we can apply CSS-filters to change the arrow color in themes */
position: absolute;
top: 100%;
right: 0;
- z-index: 2;
+ z-index: calc(var(--desktop-sidebar-z-index) + 1);
margin-top: 7px;
border-radius: 3px;
border: 1px solid var(--border-color);
@@ -1084,15 +1178,9 @@ so that we can apply CSS-filters to change the arrow color in themes */
}
.item-info .stab {
- /* This min-height is needed to unify the height of the stab elements because some of them
- have emojis.
- */
- min-height: 36px;
- display: flex;
+ display: block;
padding: 3px;
margin-bottom: 5px;
- align-items: center;
- vertical-align: text-bottom;
}
.item-name .stab {
margin-left: 0.3125em;
@@ -1115,17 +1203,26 @@ so that we can apply CSS-filters to change the arrow color in themes */
color: var(--stab-code-color);
}
-.stab .emoji {
+.stab .emoji, .item-info .stab::before {
font-size: 1.25rem;
+}
+.stab .emoji {
margin-right: 0.3rem;
}
+.item-info .stab::before {
+ /* ensure badges with emoji and without it have same height */
+ content: "\0";
+ width: 0;
+ display: inline-block;
+ color: transparent;
+}
/* Black one-pixel outline around emoji shapes */
.emoji {
text-shadow:
1px 0 0 black,
-1px 0 0 black,
- 0 1px 0 black,
+ 0 1px 0 black,
0 -1px 0 black;
}
@@ -1478,7 +1575,20 @@ a.tooltip:hover::after {
margin-left: 4px;
display: flex;
}
-#settings-menu > a, #help-button > a {
+#sidebar-button {
+ display: none;
+}
+.hide-sidebar #sidebar-button {
+ display: flex;
+ margin-right: 4px;
+ position: fixed;
+ left: 6px;
+ height: 34px;
+ width: 34px;
+ background-color: var(--main-background-color);
+ z-index: 1;
+}
+#settings-menu > a, #help-button > a, #sidebar-button > a {
display: flex;
align-items: center;
justify-content: center;
@@ -1493,10 +1603,21 @@ a.tooltip:hover::after {
}
#settings-menu > a:hover, #settings-menu > a:focus,
-#help-button > a:hover, #help-button > a:focus {
+#help-button > a:hover, #help-button > a:focus,
+#sidebar-button > a:hover, #sidebar-button > a:focus {
border-color: var(--settings-button-border-focus);
}
+#sidebar-button > a:before {
+ content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" \
+ fill="none" stroke="black">\
+ <rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5"/>\
+ <circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\
+ <path d="m7.6121 3v16 M5.375 7.625h-2 m2 3h-2 m2 3h-2" stroke-width="1.25"/></svg>');
+ width: 22px;
+ height: 22px;
+}
+
#copy-path {
color: var(--copy-path-button-color);
background: var(--main-background-color);
@@ -1711,7 +1832,7 @@ However, it's not needed with smaller screen width because the doc/code block is
/*
WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY
If you update this line, then you also need to update the line with the same warning
-in src-script.js
+in src-script.js and main.js
*/
@media (max-width: 700px) {
/* When linking to an item with an `id` (for instance, by clicking a link in the sidebar,
@@ -1722,6 +1843,10 @@ in src-script.js
scroll-margin-top: 45px;
}
+ .hide-sidebar #sidebar-button {
+ position: static;
+ }
+
.rustdoc {
/* Sidebar should overlay main content, rather than pushing main content to the right.
Turn off `display: flex` on the body element. */
@@ -1750,7 +1875,8 @@ in src-script.js
/* Hide the logo and item name from the sidebar. Those are displayed
in the mobile-topbar instead. */
.sidebar .logo-container,
- .sidebar .location {
+ .sidebar .location,
+ .sidebar-resizer {
display: none;
}
@@ -1818,6 +1944,10 @@ in src-script.js
top: 0;
}
+ .hide-sidebar .mobile-topbar {
+ display: none;
+ }
+
.sidebar-menu-toggle {
width: 45px;
/* Rare exception to specifying font sizes in rem. Since this is acting
@@ -1827,6 +1957,10 @@ in src-script.js
color: var(--main-color);
}
+ .hide-sidebar .sidebar-menu-toggle {
+ display: none;
+ }
+
.sidebar-elems {
margin-top: 1em;
}
@@ -1870,6 +2004,17 @@ in src-script.js
display: none;
}
+ /* sidebar button becomes topbar button */
+ #sidebar-button > a:before {
+ content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
+ viewBox="0 0 22 22" fill="none" stroke="black">\
+ <rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5"/>\
+ <circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\
+ <path d="m3 7.375h16m0-3h-4" stroke-width="1.25"/></svg>');
+ width: 22px;
+ height: 22px;
+ }
+
/* Display an alternating layout on tablets and phones */
.item-table, .item-row, .item-table > li, .item-table > li > div,
.search-results > a, .search-results > a > div {
@@ -2274,6 +2419,8 @@ in src-script.js
--scrape-example-help-hover-color: #000;
--scrape-example-code-wrapper-background-start: rgba(255, 255, 255, 1);
--scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0);
+ --sidebar-resizer-hover: hsl(207, 90%, 66%);
+ --sidebar-resizer-active: hsl(207, 90%, 54%);
}
/* End theme: light */
@@ -2379,6 +2526,8 @@ in src-script.js
--scrape-example-help-hover-color: #fff;
--scrape-example-code-wrapper-background-start: rgba(53, 53, 53, 1);
--scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0);
+ --sidebar-resizer-hover: hsl(207, 30%, 54%);
+ --sidebar-resizer-active: hsl(207, 90%, 54%);
}
/* End theme: dark */
@@ -2488,6 +2637,8 @@ Original by Dempfi (https://github.com/dempfi/ayu)
--scrape-example-help-hover-color: #fff;
--scrape-example-code-wrapper-background-start: rgba(15, 20, 25, 1);
--scrape-example-code-wrapper-background-end: rgba(15, 20, 25, 0);
+ --sidebar-resizer-hover: hsl(34, 50%, 33%);
+ --sidebar-resizer-active: hsl(34, 100%, 66%);
}
:root[data-theme="ayu"] h1,
@@ -2519,6 +2670,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
}
:root[data-theme="ayu"] .sidebar .current,
+:root[data-theme="ayu"] .sidebar .current a,
:root[data-theme="ayu"] .sidebar a:hover,
:root[data-theme="ayu"] #src-sidebar div.files > a:hover,
:root[data-theme="ayu"] details.dir-entry summary:hover,
@@ -2569,7 +2721,8 @@ Original by Dempfi (https://github.com/dempfi/ayu)
border-bottom: 1px solid rgba(242, 151, 24, 0.3);
}
-:root[data-theme="ayu"] #settings-menu > a img {
+:root[data-theme="ayu"] #settings-menu > a img,
+:root[data-theme="ayu"] #sidebar-button > a:before {
filter: invert(100);
}
/* End theme: ayu */
diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
index c7811b43d..93709e4e8 100644
--- a/src/librustdoc/html/static/js/externs.js
+++ b/src/librustdoc/html/static/js/externs.js
@@ -14,6 +14,7 @@ function initSearch(searchIndex){}
* pathWithoutLast: Array<string>,
* pathLast: string,
* generics: Array<QueryElement>,
+ * bindings: Map<integer, Array<QueryElement>>,
* }}
*/
let QueryElement;
@@ -24,6 +25,7 @@ let QueryElement;
* totalElems: number,
* typeFilter: (null|string),
* userQuery: string,
+ * isInBinding: (null|string),
* }}
*/
let ParserState;
@@ -40,6 +42,7 @@ let ParserState;
* totalElems: number,
* literalSearch: boolean,
* corrections: Array<{from: string, to: integer}>,
+ * typeFingerprint: Uint32Array,
* }}
*/
let ParsedQuery;
@@ -191,8 +194,9 @@ let FunctionSearchType;
/**
* @typedef {{
* id: (null|number),
- * ty: (null|number),
+ * ty: number,
* generics: Array<FunctionType>,
+ * bindings: Map<integer, Array<FunctionType>>,
* }}
*/
let FunctionType;
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 7c052606a..63ab56053 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -1,5 +1,5 @@
// Local js definitions:
-/* global addClass, getSettingValue, hasClass, searchState */
+/* global addClass, getSettingValue, hasClass, searchState, updateLocalStorage */
/* global onEach, onEachLazy, removeClass, getVar */
"use strict";
@@ -25,19 +25,9 @@ function showMain() {
removeClass(document.getElementById(MAIN_ID), "hidden");
}
-function elemIsInParent(elem, parent) {
- while (elem && elem !== document.body) {
- if (elem === parent) {
- return true;
- }
- elem = elem.parentElement;
- }
- return false;
-}
-
function blurHandler(event, parentElem, hideCallback) {
- if (!elemIsInParent(document.activeElement, parentElem) &&
- !elemIsInParent(event.relatedTarget, parentElem)
+ if (!parentElem.contains(document.activeElement) &&
+ !parentElem.contains(event.relatedTarget)
) {
hideCallback();
}
@@ -54,7 +44,7 @@ function setMobileTopbar() {
if (mobileTopbar) {
const mobileTitle = document.createElement("h2");
mobileTitle.className = "location";
- if (hasClass(document.body, "crate")) {
+ if (hasClass(document.querySelector(".rustdoc"), "crate")) {
mobileTitle.innerText = `Crate ${window.currentCrate}`;
} else if (locationTitle) {
mobileTitle.innerHTML = locationTitle.innerHTML;
@@ -485,7 +475,7 @@ function preLoadCss(cssUrl) {
return;
}
- const modpath = hasClass(document.body, "mod") ? "../" : "";
+ const modpath = hasClass(document.querySelector(".rustdoc"), "mod") ? "../" : "";
const h3 = document.createElement("h3");
h3.innerHTML = `<a href="${modpath}index.html#${id}">${longty}</a>`;
@@ -505,7 +495,7 @@ function preLoadCss(cssUrl) {
}
const link = document.createElement("a");
link.href = path;
- if (link.href === current_page) {
+ if (path === current_page) {
link.className = "current";
}
link.textContent = name;
@@ -867,12 +857,12 @@ function preLoadCss(cssUrl) {
for (const crate of window.ALL_CRATES) {
const link = document.createElement("a");
link.href = window.rootPath + crate + "/index.html";
- if (window.rootPath !== "./" && crate === window.currentCrate) {
- link.className = "current";
- }
link.textContent = crate;
const li = document.createElement("li");
+ if (window.rootPath !== "./" && crate === window.currentCrate) {
+ li.className = "current";
+ }
li.appendChild(link);
ul.appendChild(li);
}
@@ -1118,7 +1108,7 @@ function preLoadCss(cssUrl) {
if (ev.pointerType !== "mouse") {
return;
}
- if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(ev.relatedTarget, e)) {
+ if (!e.TOOLTIP_FORCE_VISIBLE && !e.contains(ev.relatedTarget)) {
// See "Tooltip pointer leave gesture" below.
setTooltipHoverTimeout(e, false);
addClass(wrapper, "fade-out");
@@ -1178,10 +1168,10 @@ function preLoadCss(cssUrl) {
function tooltipBlurHandler(event) {
if (window.CURRENT_TOOLTIP_ELEMENT &&
- !elemIsInParent(document.activeElement, window.CURRENT_TOOLTIP_ELEMENT) &&
- !elemIsInParent(event.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT) &&
- !elemIsInParent(document.activeElement, window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE) &&
- !elemIsInParent(event.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE)
+ !window.CURRENT_TOOLTIP_ELEMENT.contains(document.activeElement) &&
+ !window.CURRENT_TOOLTIP_ELEMENT.contains(event.relatedTarget) &&
+ !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(document.activeElement) &&
+ !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(event.relatedTarget)
) {
// Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.
// When I click the button on an already-opened tooltip popover, Safari
@@ -1248,8 +1238,8 @@ function preLoadCss(cssUrl) {
if (ev.pointerType !== "mouse") {
return;
}
- if (!e.TOOLTIP_FORCE_VISIBLE &&
- !elemIsInParent(ev.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT)) {
+ if (!e.TOOLTIP_FORCE_VISIBLE && window.CURRENT_TOOLTIP_ELEMENT &&
+ !window.CURRENT_TOOLTIP_ELEMENT.contains(ev.relatedTarget)) {
// Tooltip pointer leave gesture:
//
// Designing a good hover microinteraction is a matter of guessing user
@@ -1328,8 +1318,7 @@ function preLoadCss(cssUrl) {
const infos = [
`For a full list of all search features, take a look <a \
-href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
-#the-search-interface">here</a>.`,
+href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.html">here</a>.`,
"Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \
restrict the search to a given item kind.",
"Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \
@@ -1484,6 +1473,264 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
searchState.setup();
}());
+// Hide, show, and resize the sidebar
+//
+// The body class and CSS variable are initially set up in storage.js,
+// but in this file, we implement:
+//
+// * the show sidebar button, which appears if the sidebar is hidden
+// and, by clicking on it, will bring it back
+// * the sidebar resize handle, which appears only on large viewports
+// with a [fine precision pointer] to allow the user to change
+// the size of the sidebar
+//
+// [fine precision pointer]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer
+(function() {
+ // 100 is the size of the logo
+ // don't let the sidebar get smaller than that, or it'll get squished
+ const SIDEBAR_MIN = 100;
+ // Don't let the sidebar get bigger than this
+ const SIDEBAR_MAX = 500;
+ // Don't let the body (including the gutter) get smaller than this
+ //
+ // WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY
+ // Acceptable values for BODY_MIN are constrained by the mobile breakpoint
+ // (which is the minimum size of the whole page where the sidebar exists)
+ // and the default sidebar width:
+ //
+ // BODY_MIN <= RUSTDOC_MOBILE_BREAKPOINT - DEFAULT_SIDEBAR_WIDTH
+ //
+ // At the time of this writing, the DEFAULT_SIDEBAR_WIDTH on src pages is
+ // 300px, and the RUSTDOC_MOBILE_BREAKPOINT is 700px, so BODY_MIN must be
+ // at most 400px. Otherwise, it would start out at the default size, then
+ // grabbing the resize handle would suddenly cause it to jank to
+ // its contraint-generated maximum.
+ const RUSTDOC_MOBILE_BREAKPOINT = 700;
+ const BODY_MIN = 400;
+ // At half-way past the minimum size, vanish the sidebar entirely
+ const SIDEBAR_VANISH_THRESHOLD = SIDEBAR_MIN / 2;
+
+ // Toolbar button to show the sidebar.
+ //
+ // On small, "mobile-sized" viewports, it's not persistent and it
+ // can only be activated by going into Settings and hiding the nav bar.
+ // On larger, "desktop-sized" viewports (though that includes many
+ // tablets), it's fixed-position, appears in the left side margin,
+ // and it can be activated by resizing the sidebar into nothing.
+ const sidebarButton = document.getElementById("sidebar-button");
+ if (sidebarButton) {
+ sidebarButton.addEventListener("click", e => {
+ removeClass(document.documentElement, "hide-sidebar");
+ updateLocalStorage("hide-sidebar", "false");
+ e.preventDefault();
+ });
+ }
+
+ // Pointer capture.
+ //
+ // Resizing is a single-pointer gesture. Any secondary pointer is ignored
+ let currentPointerId = null;
+
+ // "Desired" sidebar size.
+ //
+ // This is stashed here for window resizing. If the sidebar gets
+ // shrunk to maintain BODY_MIN, and then the user grows the window again,
+ // it gets the sidebar to restore its size.
+ let desiredSidebarSize = null;
+
+ // Sidebar resize debouncer.
+ //
+ // The sidebar itself is resized instantly, but the body HTML can be too
+ // big for that, causing reflow jank. To reduce this, we queue up a separate
+ // animation frame and throttle it.
+ let pendingSidebarResizingFrame = false;
+
+ // If this page has no sidebar at all, bail out.
+ const resizer = document.querySelector(".sidebar-resizer");
+ const sidebar = document.querySelector(".sidebar");
+ if (!resizer || !sidebar) {
+ return;
+ }
+
+ // src page and docs page use different variables, because the contents of
+ // the sidebar are so different that it's reasonable to thing the user
+ // would want them to have different sizes
+ const isSrcPage = hasClass(document.body, "src");
+
+ // Call this function to hide the sidebar when using the resize handle
+ //
+ // This function also nulls out the sidebar width CSS variable and setting,
+ // causing it to return to its default. This does not happen if you do it
+ // from settings.js, which uses a separate function. It's done here because
+ // the minimum sidebar size is rather uncomfortable, and it must pass
+ // through that size when using the shrink-to-nothing gesture.
+ function hideSidebar() {
+ if (isSrcPage) {
+ window.rustdocCloseSourceSidebar();
+ updateLocalStorage("src-sidebar-width", null);
+ // [RUSTDOCIMPL] CSS variable fast path
+ //
+ // The sidebar width variable is attached to the <html> element by
+ // storage.js, because the sidebar and resizer don't exist yet.
+ // But the resize code, in `resize()`, sets the property on the
+ // sidebar and resizer elements (which are the only elements that
+ // use the variable) to avoid recalculating CSS on the entire
+ // document on every frame.
+ //
+ // So, to clear it, we need to clear all three.
+ document.documentElement.style.removeProperty("--src-sidebar-width");
+ sidebar.style.removeProperty("--src-sidebar-width");
+ resizer.style.removeProperty("--src-sidebar-width");
+ } else {
+ addClass(document.documentElement, "hide-sidebar");
+ updateLocalStorage("hide-sidebar", "true");
+ updateLocalStorage("desktop-sidebar-width", null);
+ document.documentElement.style.removeProperty("--desktop-sidebar-width");
+ sidebar.style.removeProperty("--desktop-sidebar-width");
+ resizer.style.removeProperty("--desktop-sidebar-width");
+ }
+ }
+
+ // Call this function to show the sidebar from the resize handle.
+ // On docs pages, this can only happen if the user has grabbed the resize
+ // handle, shrunk the sidebar down to nothing, and then pulls back into
+ // the visible range without releasing it. You can, however, grab the
+ // resize handle on a source page with the sidebar closed, because it
+ // remains visible all the time on there.
+ function showSidebar() {
+ if (isSrcPage) {
+ window.rustdocShowSourceSidebar();
+ } else {
+ removeClass(document.documentElement, "hide-sidebar");
+ updateLocalStorage("hide-sidebar", "false");
+ }
+ }
+
+ /**
+ * Call this to set the correct CSS variable and setting.
+ * This function doesn't enforce size constraints. Do that before calling it!
+ *
+ * @param {number} size - CSS px width of the sidebar.
+ */
+ function changeSidebarSize(size) {
+ if (isSrcPage) {
+ updateLocalStorage("src-sidebar-width", size);
+ // [RUSTDOCIMPL] CSS variable fast path
+ //
+ // While this property is set on the HTML element at load time,
+ // because the sidebar isn't actually loaded yet,
+ // we scope this update to the sidebar to avoid hitting a slow
+ // path in WebKit.
+ sidebar.style.setProperty("--src-sidebar-width", size + "px");
+ resizer.style.setProperty("--src-sidebar-width", size + "px");
+ } else {
+ updateLocalStorage("desktop-sidebar-width", size);
+ sidebar.style.setProperty("--desktop-sidebar-width", size + "px");
+ resizer.style.setProperty("--desktop-sidebar-width", size + "px");
+ }
+ }
+
+ // Check if the sidebar is hidden. Since src pages and doc pages have
+ // different settings, this function has to check that.
+ function isSidebarHidden() {
+ return isSrcPage ?
+ !hasClass(document.documentElement, "src-sidebar-expanded") :
+ hasClass(document.documentElement, "hide-sidebar");
+ }
+
+ // Respond to the resize handle event.
+ // This function enforces size constraints, and implements the
+ // shrink-to-nothing gesture based on thresholds defined above.
+ function resize(e) {
+ if (currentPointerId === null || currentPointerId !== e.pointerId) {
+ return;
+ }
+ e.preventDefault();
+ const pos = e.clientX - sidebar.offsetLeft - 3;
+ if (pos < SIDEBAR_VANISH_THRESHOLD) {
+ hideSidebar();
+ } else if (pos >= SIDEBAR_MIN) {
+ if (isSidebarHidden()) {
+ showSidebar();
+ }
+ // don't let the sidebar get wider than SIDEBAR_MAX, or the body narrower
+ // than BODY_MIN
+ const constrainedPos = Math.min(pos, window.innerWidth - BODY_MIN, SIDEBAR_MAX);
+ changeSidebarSize(constrainedPos);
+ desiredSidebarSize = constrainedPos;
+ if (pendingSidebarResizingFrame !== false) {
+ clearTimeout(pendingSidebarResizingFrame);
+ }
+ pendingSidebarResizingFrame = setTimeout(() => {
+ if (currentPointerId === null || pendingSidebarResizingFrame === false) {
+ return;
+ }
+ pendingSidebarResizingFrame = false;
+ document.documentElement.style.setProperty(
+ "--resizing-sidebar-width",
+ desiredSidebarSize + "px"
+ );
+ }, 100);
+ }
+ }
+ // Respond to the window resize event.
+ window.addEventListener("resize", () => {
+ if (window.innerWidth < RUSTDOC_MOBILE_BREAKPOINT) {
+ return;
+ }
+ stopResize();
+ if (desiredSidebarSize >= (window.innerWidth - BODY_MIN)) {
+ changeSidebarSize(window.innerWidth - BODY_MIN);
+ } else if (desiredSidebarSize !== null && desiredSidebarSize > SIDEBAR_MIN) {
+ changeSidebarSize(desiredSidebarSize);
+ }
+ });
+ function stopResize(e) {
+ if (currentPointerId === null) {
+ return;
+ }
+ if (e) {
+ e.preventDefault();
+ }
+ desiredSidebarSize = sidebar.getBoundingClientRect().width;
+ removeClass(resizer, "active");
+ window.removeEventListener("pointermove", resize, false);
+ window.removeEventListener("pointerup", stopResize, false);
+ removeClass(document.documentElement, "sidebar-resizing");
+ document.documentElement.style.removeProperty( "--resizing-sidebar-width");
+ if (resizer.releasePointerCapture) {
+ resizer.releasePointerCapture(currentPointerId);
+ currentPointerId = null;
+ }
+ }
+ function initResize(e) {
+ if (currentPointerId !== null || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
+ return;
+ }
+ if (resizer.setPointerCapture) {
+ resizer.setPointerCapture(e.pointerId);
+ if (!resizer.hasPointerCapture(e.pointerId)) {
+ // unable to capture pointer; something else has it
+ // on iOS, this usually means you long-clicked a link instead
+ resizer.releasePointerCapture(e.pointerId);
+ return;
+ }
+ currentPointerId = e.pointerId;
+ }
+ e.preventDefault();
+ window.addEventListener("pointermove", resize, false);
+ window.addEventListener("pointercancel", stopResize, false);
+ window.addEventListener("pointerup", stopResize, false);
+ addClass(resizer, "active");
+ addClass(document.documentElement, "sidebar-resizing");
+ const pos = e.clientX - sidebar.offsetLeft - 3;
+ document.documentElement.style.setProperty( "--resizing-sidebar-width", pos + "px");
+ desiredSidebarSize = null;
+ }
+ resizer.addEventListener("pointerdown", initResize, false);
+}());
+
+// This section handles the copy button that appears next to the path breadcrumbs
(function() {
let reset_button_timeout = null;
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 48c9a53a2..e824a1fd4 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -18,36 +18,38 @@ if (!Array.prototype.toSpliced) {
// This mapping table should match the discriminants of
// `rustdoc::formats::item_type::ItemType` type in Rust.
const itemTypes = [
+ "keyword",
+ "primitive",
"mod",
"externcrate",
"import",
- "struct",
+ "struct", // 5
"enum",
"fn",
"type",
"static",
- "trait",
+ "trait", // 10
"impl",
"tymethod",
"method",
"structfield",
- "variant",
+ "variant", // 15
"macro",
- "primitive",
"associatedtype",
"constant",
"associatedconstant",
- "union",
+ "union", // 20
"foreigntype",
- "keyword",
"existential",
"attr",
"derive",
- "traitalias",
+ "traitalias", // 25
"generic",
];
const longItemTypes = [
+ "keyword",
+ "primitive type",
"module",
"extern crate",
"re-export",
@@ -63,13 +65,11 @@ const longItemTypes = [
"struct field",
"enum variant",
"macro",
- "primitive type",
"assoc type",
"constant",
"assoc const",
"union",
"foreign type",
- "keyword",
"existential type",
"attribute macro",
"derive macro",
@@ -77,15 +77,9 @@ const longItemTypes = [
];
// used for special search precedence
-const TY_PRIMITIVE = itemTypes.indexOf("primitive");
-const TY_KEYWORD = itemTypes.indexOf("keyword");
const TY_GENERIC = itemTypes.indexOf("generic");
const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
-function hasOwnPropertyRustdoc(obj, property) {
- return Object.prototype.hasOwnProperty.call(obj, property);
-}
-
// In the search display, allows to switch between tabs.
function printTab(nb) {
let iter = 0;
@@ -240,12 +234,16 @@ function initSearch(rawSearchIndex) {
* @type {Array<Row>}
*/
let searchIndex;
+ /**
+ * @type {Uint32Array}
+ */
+ let functionTypeFingerprint;
let currentResults;
/**
* Map from normalized type names to integers. Used to make type search
* more efficient.
*
- * @type {Map<string, integer>}
+ * @type {Map<string, {id: integer, assocOnly: boolean}>}
*/
let typeNameIdMap;
const ALIASES = new Map();
@@ -272,37 +270,32 @@ function initSearch(rawSearchIndex) {
* get the same ID.
*
* @param {string} name
+ * @param {boolean} isAssocType - True if this is an assoc type
*
* @returns {integer}
*/
- function buildTypeMapIndex(name) {
+ function buildTypeMapIndex(name, isAssocType) {
if (name === "" || name === null) {
return null;
}
if (typeNameIdMap.has(name)) {
- return typeNameIdMap.get(name);
+ const obj = typeNameIdMap.get(name);
+ obj.assocOnly = isAssocType && obj.assocOnly;
+ return obj.id;
} else {
const id = typeNameIdMap.size;
- typeNameIdMap.set(name, id);
+ typeNameIdMap.set(name, {id, assocOnly: isAssocType});
return id;
}
}
- function isWhitespace(c) {
- return " \t\n\r".indexOf(c) !== -1;
- }
-
function isSpecialStartCharacter(c) {
return "<\"".indexOf(c) !== -1;
}
function isEndCharacter(c) {
- return ",>-]".indexOf(c) !== -1;
- }
-
- function isStopCharacter(c) {
- return isEndCharacter(c);
+ return "=,>-]".indexOf(c) !== -1;
}
function isErrorCharacter(c) {
@@ -398,7 +391,7 @@ function initSearch(rawSearchIndex) {
* @return {boolean}
*/
function isSeparatorCharacter(c) {
- return c === ",";
+ return c === "," || c === "=";
}
/**
@@ -410,7 +403,7 @@ function initSearch(rawSearchIndex) {
* @return {boolean}
*/
function isPathSeparator(c) {
- return c === ":" || isWhitespace(c);
+ return c === ":" || c === " ";
}
/**
@@ -427,7 +420,7 @@ function initSearch(rawSearchIndex) {
const c = parserState.userQuery[pos - 1];
if (c === lookingFor) {
return true;
- } else if (!isWhitespace(c)) {
+ } else if (c !== " ") {
break;
}
pos -= 1;
@@ -456,7 +449,7 @@ function initSearch(rawSearchIndex) {
function skipWhitespace(parserState) {
while (parserState.pos < parserState.userQuery.length) {
const c = parserState.userQuery[parserState.pos];
- if (!isWhitespace(c)) {
+ if (c !== " ") {
break;
}
parserState.pos += 1;
@@ -475,8 +468,6 @@ function initSearch(rawSearchIndex) {
const path = name.trim();
if (path.length === 0 && generics.length === 0) {
throw ["Unexpected ", parserState.userQuery[parserState.pos]];
- } else if (path === "*") {
- throw ["Unexpected ", "*"];
}
if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {
throw ["Cannot have more than one element if you use quotes"];
@@ -500,28 +491,30 @@ function initSearch(rawSearchIndex) {
" does not accept generic parameters",
];
}
+ const bindingName = parserState.isInBinding;
+ parserState.isInBinding = null;
return {
name: "never",
id: null,
fullPath: ["never"],
pathWithoutLast: [],
pathLast: "never",
+ normalizedPathLast: "never",
generics: [],
+ bindings: new Map(),
typeFilter: "primitive",
+ bindingName,
};
}
+ const quadcolon = /::\s*::/.exec(path);
if (path.startsWith("::")) {
throw ["Paths cannot start with ", "::"];
} else if (path.endsWith("::")) {
throw ["Paths cannot end with ", "::"];
- } else if (path.includes("::::")) {
- throw ["Unexpected ", "::::"];
- } else if (path.includes(" ::")) {
- throw ["Unexpected ", " ::"];
- } else if (path.includes(":: ")) {
- throw ["Unexpected ", ":: "];
- }
- const pathSegments = path.split(/::|\s+/);
+ } else if (quadcolon !== null) {
+ throw ["Unexpected ", quadcolon[0]];
+ }
+ const pathSegments = path.split(/(?:::\s*)|(?:\s+(?:::\s*)?)/);
// In case we only have something like `<p>`, there is no name.
if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) {
if (generics.length > 0 || prevIs(parserState, ">")) {
@@ -542,14 +535,29 @@ function initSearch(rawSearchIndex) {
if (isInGenerics) {
parserState.genericsElems += 1;
}
+ const bindingName = parserState.isInBinding;
+ parserState.isInBinding = null;
+ const bindings = new Map();
+ const pathLast = pathSegments[pathSegments.length - 1];
return {
name: name.trim(),
id: null,
fullPath: pathSegments,
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
- pathLast: pathSegments[pathSegments.length - 1],
- generics: generics,
+ pathLast,
+ normalizedPathLast: pathLast.replace(/_/g, ""),
+ generics: generics.filter(gen => {
+ // Syntactically, bindings are parsed as generics,
+ // but the query engine treats them differently.
+ if (gen.bindingName !== null) {
+ bindings.set(gen.bindingName.name, [gen, ...gen.bindingName.generics]);
+ return false;
+ }
+ return true;
+ }),
+ bindings,
typeFilter,
+ bindingName,
};
}
@@ -589,7 +597,7 @@ function initSearch(rawSearchIndex) {
} else {
while (parserState.pos + 1 < parserState.length) {
const next_c = parserState.userQuery[parserState.pos + 1];
- if (!isWhitespace(next_c)) {
+ if (next_c !== " ") {
break;
}
parserState.pos += 1;
@@ -608,7 +616,7 @@ function initSearch(rawSearchIndex) {
}
} else if (
c === "[" ||
- isStopCharacter(c) ||
+ isEndCharacter(c) ||
isSpecialStartCharacter(c) ||
isSeparatorCharacter(c)
) {
@@ -657,6 +665,7 @@ function initSearch(rawSearchIndex) {
parserState.pos += 1;
getItemsBefore(query, parserState, generics, "]");
const typeFilter = parserState.typeFilter;
+ const isInBinding = parserState.isInBinding;
if (typeFilter !== null && typeFilter !== "primitive") {
throw [
"Invalid search type: primitive ",
@@ -667,18 +676,27 @@ function initSearch(rawSearchIndex) {
];
}
parserState.typeFilter = null;
+ parserState.isInBinding = null;
parserState.totalElems += 1;
if (isInGenerics) {
parserState.genericsElems += 1;
}
+ for (const gen of generics) {
+ if (gen.bindingName !== null) {
+ throw ["Type parameter ", "=", " cannot be within slice ", "[]"];
+ }
+ }
elems.push({
name: "[]",
id: null,
fullPath: ["[]"],
pathWithoutLast: [],
pathLast: "[]",
+ normalizedPathLast: "[]",
generics,
typeFilter: "primitive",
+ bindingName: isInBinding,
+ bindings: new Map(),
});
} else {
const isStringElem = parserState.userQuery[start] === "\"";
@@ -705,15 +723,38 @@ function initSearch(rawSearchIndex) {
if (start >= end && generics.length === 0) {
return;
}
- elems.push(
- createQueryElement(
- query,
- parserState,
- parserState.userQuery.slice(start, end),
- generics,
- isInGenerics
- )
- );
+ if (parserState.userQuery[parserState.pos] === "=") {
+ if (parserState.isInBinding) {
+ throw ["Cannot write ", "=", " twice in a binding"];
+ }
+ if (!isInGenerics) {
+ throw ["Type parameter ", "=", " must be within generics list"];
+ }
+ const name = parserState.userQuery.slice(start, end).trim();
+ if (name === "!") {
+ throw ["Type parameter ", "=", " key cannot be ", "!", " never type"];
+ }
+ if (name.includes("!")) {
+ throw ["Type parameter ", "=", " key cannot be ", "!", " macro"];
+ }
+ if (name.includes("::")) {
+ throw ["Type parameter ", "=", " key cannot contain ", "::", " path"];
+ }
+ if (name.includes(":")) {
+ throw ["Type parameter ", "=", " key cannot contain ", ":", " type"];
+ }
+ parserState.isInBinding = { name, generics };
+ } else {
+ elems.push(
+ createQueryElement(
+ query,
+ parserState,
+ parserState.userQuery.slice(start, end),
+ generics,
+ isInGenerics
+ )
+ );
+ }
}
}
@@ -737,6 +778,8 @@ function initSearch(rawSearchIndex) {
// If this is a generic, keep the outer item's type filter around.
const oldTypeFilter = parserState.typeFilter;
parserState.typeFilter = null;
+ const oldIsInBinding = parserState.isInBinding;
+ parserState.isInBinding = null;
let extra = "";
if (endChar === ">") {
@@ -752,6 +795,9 @@ function initSearch(rawSearchIndex) {
while (parserState.pos < parserState.length) {
const c = parserState.userQuery[parserState.pos];
if (c === endChar) {
+ if (parserState.isInBinding) {
+ throw ["Unexpected ", endChar, " after ", "="];
+ }
break;
} else if (isSeparatorCharacter(c)) {
parserState.pos += 1;
@@ -791,7 +837,9 @@ function initSearch(rawSearchIndex) {
throw [
"Expected ",
",",
- " or ",
+ ", ",
+ "=",
+ ", or ",
endChar,
...extra,
", found ",
@@ -801,6 +849,8 @@ function initSearch(rawSearchIndex) {
throw [
"Expected ",
",",
+ " or ",
+ "=",
...extra,
", found ",
c,
@@ -828,6 +878,7 @@ function initSearch(rawSearchIndex) {
parserState.pos += 1;
parserState.typeFilter = oldTypeFilter;
+ parserState.isInBinding = oldIsInBinding;
}
/**
@@ -865,7 +916,7 @@ function initSearch(rawSearchIndex) {
while (parserState.pos < parserState.length) {
const c = parserState.userQuery[parserState.pos];
- if (isStopCharacter(c)) {
+ if (isEndCharacter(c)) {
foundStopChar = true;
if (isSeparatorCharacter(c)) {
parserState.pos += 1;
@@ -900,7 +951,7 @@ function initSearch(rawSearchIndex) {
query.literalSearch = false;
foundStopChar = true;
continue;
- } else if (isWhitespace(c)) {
+ } else if (c === " ") {
skipWhitespace(parserState);
continue;
}
@@ -991,6 +1042,8 @@ function initSearch(rawSearchIndex) {
correction: null,
proposeCorrectionFrom: null,
proposeCorrectionTo: null,
+ // bloom filter build from type ids
+ typeFingerprint: new Uint32Array(4),
};
}
@@ -1021,7 +1074,7 @@ function initSearch(rawSearchIndex) {
if (elem &&
elem.value !== "all crates" &&
- hasOwnPropertyRustdoc(rawSearchIndex, elem.value)
+ rawSearchIndex.has(elem.value)
) {
return elem.value;
}
@@ -1054,8 +1107,13 @@ function initSearch(rawSearchIndex) {
for (const elem2 of elem.generics) {
convertTypeFilterOnElem(elem2);
}
+ for (const constraints of elem.bindings.values()) {
+ for (const constraint of constraints) {
+ convertTypeFilterOnElem(constraint);
+ }
+ }
}
- userQuery = userQuery.trim();
+ userQuery = userQuery.trim().replace(/\r|\n|\t/g, " ");
const parserState = {
length: userQuery.length,
pos: 0,
@@ -1063,6 +1121,7 @@ function initSearch(rawSearchIndex) {
totalElems: 0,
genericsElems: 0,
typeFilter: null,
+ isInBinding: null,
userQuery: userQuery.toLowerCase(),
};
let query = newParsedQuery(userQuery);
@@ -1080,7 +1139,6 @@ function initSearch(rawSearchIndex) {
query.error = err;
return query;
}
-
if (!query.literalSearch) {
// If there is more than one element in the query, we switch to literalSearch in any
// case.
@@ -1114,13 +1172,12 @@ function initSearch(rawSearchIndex) {
* Executes the parsed query and builds a {ResultsTable}.
*
* @param {ParsedQuery} parsedQuery - The parsed user query
- * @param {Object} searchWords - The list of search words to query against
* @param {Object} [filterCrates] - Crate to search in if defined
* @param {Object} [currentCrate] - Current crate, to rank results from this crate higher
*
* @return {ResultsTable}
*/
- function execQuery(parsedQuery, searchWords, filterCrates, currentCrate) {
+ function execQuery(parsedQuery, filterCrates, currentCrate) {
const results_others = new Map(), results_in_args = new Map(),
results_returned = new Map();
@@ -1178,8 +1235,8 @@ function initSearch(rawSearchIndex) {
const userQuery = parsedQuery.userQuery;
const result_list = [];
for (const result of results.values()) {
- result.word = searchWords[result.id];
- result.item = searchIndex[result.id] || {};
+ result.item = searchIndex[result.id];
+ result.word = searchIndex[result.id].word;
result_list.push(result);
}
@@ -1251,16 +1308,6 @@ function initSearch(rawSearchIndex) {
return (a > b ? +1 : -1);
}
- // special precedence for primitive and keyword pages
- if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) ||
- (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) {
- return -1;
- }
- if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) ||
- (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) {
- return 1;
- }
-
// sort by description (no description goes later)
a = (aaa.item.desc === "");
b = (bbb.item.desc === "");
@@ -1286,59 +1333,10 @@ function initSearch(rawSearchIndex) {
return 0;
});
- let nameSplit = null;
- if (parsedQuery.elems.length === 1) {
- const hasPath = typeof parsedQuery.elems[0].path === "undefined";
- nameSplit = hasPath ? null : parsedQuery.elems[0].path;
- }
-
- for (const result of result_list) {
- // this validation does not make sense when searching by types
- if (result.dontValidate) {
- continue;
- }
- const name = result.item.name.toLowerCase(),
- path = result.item.path.toLowerCase(),
- parent = result.item.parent;
-
- if (!isType && !validateResult(name, path, nameSplit, parent)) {
- result.id = -1;
- }
- }
return transformResults(result_list);
}
/**
- * This function checks generics in search query `queryElem` can all be found in the
- * search index (`fnType`),
- *
- * This function returns `true` if it matches, and also writes the results to mgensInout.
- * It returns `false` if no match is found, and leaves mgensInout untouched.
- *
- * @param {FunctionType} fnType - The object to check.
- * @param {QueryElement} queryElem - The element from the parsed query.
- * @param {[FunctionType]} whereClause - Trait bounds for generic items.
- * @param {Map<number,number>|null} mgensInout - Map functions generics to query generics.
- *
- * @return {boolean} - Returns true if a match, false otherwise.
- */
- function checkGenerics(fnType, queryElem, whereClause, mgensInout) {
- return unifyFunctionTypes(
- fnType.generics,
- queryElem.generics,
- whereClause,
- mgensInout,
- mgens => {
- if (mgensInout) {
- for (const [fid, qid] of mgens.entries()) {
- mgensInout.set(fid, qid);
- }
- }
- return true;
- }
- );
- }
- /**
* This function checks if a list of search query `queryElems` can all be found in the
* search index (`fnTypes`).
*
@@ -1348,7 +1346,7 @@ function initSearch(rawSearchIndex) {
* then this function will try with a different solution, or bail with false if it
* runs out of candidates.
*
- * @param {Array<FunctionType>} fnTypes - The objects to check.
+ * @param {Array<FunctionType>} fnTypesIn - The objects to check.
* @param {Array<QueryElement>} queryElems - The elements from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
* @param {Map<number,number>|null} mgensIn
@@ -1359,9 +1357,9 @@ function initSearch(rawSearchIndex) {
*/
function unifyFunctionTypes(fnTypesIn, queryElems, whereClause, mgensIn, solutionCb) {
/**
- * @type Map<integer, integer>
+ * @type Map<integer, integer>|null
*/
- let mgens = new Map(mgensIn);
+ const mgens = mgensIn === null ? null : new Map(mgensIn);
if (queryElems.length === 0) {
return !solutionCb || solutionCb(mgens);
}
@@ -1369,204 +1367,249 @@ function initSearch(rawSearchIndex) {
return false;
}
const ql = queryElems.length;
- let fl = fnTypesIn.length;
+ const fl = fnTypesIn.length;
+
+ // One element fast path / base case
+ if (ql === 1 && queryElems[0].generics.length === 0
+ && queryElems[0].bindings.size === 0) {
+ const queryElem = queryElems[0];
+ for (const fnType of fnTypesIn) {
+ if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
+ continue;
+ }
+ if (fnType.id < 0 && queryElem.id < 0) {
+ if (mgens && mgens.has(fnType.id) &&
+ mgens.get(fnType.id) !== queryElem.id) {
+ continue;
+ }
+ const mgensScratch = new Map(mgens);
+ mgensScratch.set(fnType.id, queryElem.id);
+ if (!solutionCb || solutionCb(mgensScratch)) {
+ return true;
+ }
+ } else if (!solutionCb || solutionCb(mgens ? new Map(mgens) : null)) {
+ // unifyFunctionTypeIsMatchCandidate already checks that ids match
+ return true;
+ }
+ }
+ for (const fnType of fnTypesIn) {
+ if (!unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) {
+ continue;
+ }
+ if (fnType.id < 0) {
+ if (mgens && mgens.has(fnType.id) &&
+ mgens.get(fnType.id) !== 0) {
+ continue;
+ }
+ const mgensScratch = new Map(mgens);
+ mgensScratch.set(fnType.id, 0);
+ if (unifyFunctionTypes(
+ whereClause[(-fnType.id) - 1],
+ queryElems,
+ whereClause,
+ mgensScratch,
+ solutionCb
+ )) {
+ return true;
+ }
+ } else if (unifyFunctionTypes(
+ [...fnType.generics, ...Array.from(fnType.bindings.values()).flat() ],
+ queryElems,
+ whereClause,
+ mgens ? new Map(mgens) : null,
+ solutionCb
+ )) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Multiple element recursive case
/**
* @type Array<FunctionType>
*/
- let fnTypes = fnTypesIn.slice();
+ const fnTypes = fnTypesIn.slice();
/**
- * loop works by building up a solution set in the working arrays
+ * Algorithm works by building up a solution set in the working arrays
* fnTypes gets mutated in place to make this work, while queryElems
- * is left alone
+ * is left alone.
+ *
+ * It works backwards, because arrays can be cheaply truncated that way.
*
- * vvvvvvv `i` points here
- * queryElems = [ good, good, good, unknown, unknown ],
- * fnTypes = [ good, good, good, unknown, unknown ],
- * ---------------- ^^^^^^^^^^^^^^^^ `j` iterates after `i`,
- * | looking for candidates
- * everything before `i` is the
- * current working solution
+ * vvvvvvv `queryElem`
+ * queryElems = [ unknown, unknown, good, good, good ]
+ * fnTypes = [ unknown, unknown, good, good, good ]
+ * ^^^^^^^^^^^^^^^^ loop over these elements to find candidates
*
* Everything in the current working solution is known to be a good
* match, but it might not be the match we wind up going with, because
* there might be more than one candidate match, and we need to try them all
* before giving up. So, to handle this, it backtracks on failure.
- *
- * @type Array<{
- * "fnTypesScratch": Array<FunctionType>,
- * "queryElemsOffset": integer,
- * "fnTypesOffset": integer
- * }>
*/
- const backtracking = [];
- let i = 0;
- let j = 0;
- const backtrack = () => {
- while (backtracking.length !== 0) {
- // this session failed, but there are other possible solutions
- // to backtrack, reset to (a copy of) the old array, do the swap or unboxing
- const {
- fnTypesScratch,
- mgensScratch,
- queryElemsOffset,
- fnTypesOffset,
- unbox,
- } = backtracking.pop();
- mgens = new Map(mgensScratch);
- const fnType = fnTypesScratch[fnTypesOffset];
- const queryElem = queryElems[queryElemsOffset];
- if (unbox) {
- if (fnType.id < 0) {
- if (mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
- continue;
- }
- mgens.set(fnType.id, 0);
- }
- const generics = fnType.id < 0 ?
- whereClause[(-fnType.id) - 1] :
- fnType.generics;
- fnTypes = fnTypesScratch.toSpliced(fnTypesOffset, 1, ...generics);
- fl = fnTypes.length;
- // re-run the matching algorithm on this item
- i = queryElemsOffset - 1;
- } else {
- if (fnType.id < 0) {
- if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) {
- continue;
- }
- mgens.set(fnType.id, queryElem.id);
- }
- fnTypes = fnTypesScratch.slice();
- fl = fnTypes.length;
- const tmp = fnTypes[queryElemsOffset];
- fnTypes[queryElemsOffset] = fnTypes[fnTypesOffset];
- fnTypes[fnTypesOffset] = tmp;
- // this is known as a good match; go to the next one
- i = queryElemsOffset;
- }
- return true;
+ const flast = fl - 1;
+ const qlast = ql - 1;
+ const queryElem = queryElems[qlast];
+ let queryElemsTmp = null;
+ for (let i = flast; i >= 0; i -= 1) {
+ const fnType = fnTypes[i];
+ if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
+ continue;
}
- return false;
- };
- for (i = 0; i !== ql; ++i) {
- const queryElem = queryElems[i];
- /**
- * list of potential function types that go with the current query element.
- * @type Array<integer>
- */
- const matchCandidates = [];
- let fnTypesScratch = null;
- let mgensScratch = null;
- // don't try anything before `i`, because they've already been
- // paired off with the other query elements
- for (j = i; j !== fl; ++j) {
- const fnType = fnTypes[j];
- if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
- if (!fnTypesScratch) {
- fnTypesScratch = fnTypes.slice();
+ let mgensScratch;
+ if (fnType.id < 0) {
+ mgensScratch = new Map(mgens);
+ if (mgensScratch.has(fnType.id)
+ && mgensScratch.get(fnType.id) !== queryElem.id) {
+ continue;
+ }
+ mgensScratch.set(fnType.id, queryElem.id);
+ } else {
+ mgensScratch = mgens;
+ }
+ // fnTypes[i] is a potential match
+ // fnTypes[flast] is the last item in the list
+ // swap them, and drop the potential match from the list
+ // check if the remaining function types also match
+ fnTypes[i] = fnTypes[flast];
+ fnTypes.length = flast;
+ if (!queryElemsTmp) {
+ queryElemsTmp = queryElems.slice(0, qlast);
+ }
+ const passesUnification = unifyFunctionTypes(
+ fnTypes,
+ queryElemsTmp,
+ whereClause,
+ mgensScratch,
+ mgensScratch => {
+ if (fnType.generics.length === 0 && queryElem.generics.length === 0
+ && fnType.bindings.size === 0 && queryElem.bindings.size === 0) {
+ return !solutionCb || solutionCb(mgensScratch);
}
- unifyFunctionTypes(
- fnType.generics,
- queryElem.generics,
+ const solution = unifyFunctionTypeCheckBindings(
+ fnType,
+ queryElem,
whereClause,
- mgens,
- mgensScratch => {
- matchCandidates.push({
- fnTypesScratch,
- mgensScratch,
- queryElemsOffset: i,
- fnTypesOffset: j,
- unbox: false,
- });
- return false; // "reject" all candidates to gather all of them
- }
+ mgensScratch
);
- }
- if (unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) {
- if (!fnTypesScratch) {
- fnTypesScratch = fnTypes.slice();
+ if (!solution) {
+ return false;
}
- if (!mgensScratch) {
- mgensScratch = new Map(mgens);
+ const simplifiedGenerics = solution.simplifiedGenerics;
+ for (const simplifiedMgens of solution.mgens) {
+ const passesUnification = unifyFunctionTypes(
+ simplifiedGenerics,
+ queryElem.generics,
+ whereClause,
+ simplifiedMgens,
+ solutionCb
+ );
+ if (passesUnification) {
+ return true;
+ }
}
- backtracking.push({
- fnTypesScratch,
- mgensScratch,
- queryElemsOffset: i,
- fnTypesOffset: j,
- unbox: true,
- });
- }
- }
- if (matchCandidates.length === 0) {
- if (backtrack()) {
- continue;
- } else {
return false;
}
+ );
+ if (passesUnification) {
+ return true;
}
- // use the current candidate
- const {fnTypesOffset: candidate, mgensScratch: mgensNew} = matchCandidates.pop();
- if (fnTypes[candidate].id < 0 && queryElems[i].id < 0) {
- mgens.set(fnTypes[candidate].id, queryElems[i].id);
- }
- for (const [fid, qid] of mgensNew) {
- mgens.set(fid, qid);
- }
- // `i` and `j` are paired off
- // `queryElems[i]` is left in place
- // `fnTypes[j]` is swapped with `fnTypes[i]` to pair them off
- const tmp = fnTypes[candidate];
- fnTypes[candidate] = fnTypes[i];
- fnTypes[i] = tmp;
- // write other candidates to backtracking queue
- for (const otherCandidate of matchCandidates) {
- backtracking.push(otherCandidate);
- }
- // If we're on the last item, check the solution with the callback
- // backtrack if the callback says its unsuitable
- while (i === (ql - 1) && solutionCb && !solutionCb(mgens)) {
- if (!backtrack()) {
- return false;
+ // backtrack
+ fnTypes[flast] = fnTypes[i];
+ fnTypes[i] = fnType;
+ fnTypes.length = fl;
+ }
+ for (let i = flast; i >= 0; i -= 1) {
+ const fnType = fnTypes[i];
+ if (!unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) {
+ continue;
+ }
+ let mgensScratch;
+ if (fnType.id < 0) {
+ mgensScratch = new Map(mgens);
+ if (mgensScratch.has(fnType.id) && mgensScratch.get(fnType.id) !== 0) {
+ continue;
}
+ mgensScratch.set(fnType.id, 0);
+ } else {
+ mgensScratch = mgens;
+ }
+ const generics = fnType.id < 0 ?
+ whereClause[(-fnType.id) - 1] :
+ fnType.generics;
+ const bindings = fnType.bindings ?
+ Array.from(fnType.bindings.values()).flat() :
+ [];
+ const passesUnification = unifyFunctionTypes(
+ fnTypes.toSpliced(i, 1, ...generics, ...bindings),
+ queryElems,
+ whereClause,
+ mgensScratch,
+ solutionCb
+ );
+ if (passesUnification) {
+ return true;
}
}
- return true;
+ return false;
}
- function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens) {
+ /**
+ * Check if this function is a match candidate.
+ *
+ * This function is all the fast checks that don't require backtracking.
+ * It checks that two items are not named differently, and is load-bearing for that.
+ * It also checks that, if the query has generics, the function type must have generics
+ * or associated type bindings: that's not load-bearing, but it prevents unnecessary
+ * backtracking later.
+ *
+ * @param {FunctionType} fnType
+ * @param {QueryElement} queryElem
+ * @param {[FunctionSearchType]} whereClause - Trait bounds for generic items.
+ * @param {Map<number,number>|null} mgensIn - Map functions generics to query generics.
+ * @returns {boolean}
+ */
+ function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgensIn) {
// type filters look like `trait:Read` or `enum:Result`
if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
return false;
}
// fnType.id < 0 means generic
// queryElem.id < 0 does too
- // mgens[fnType.id] = queryElem.id
- // or, if mgens[fnType.id] = 0, then we've matched this generic with a bare trait
+ // mgensIn[fnType.id] = queryElem.id
+ // or, if mgensIn[fnType.id] = 0, then we've matched this generic with a bare trait
// and should make that same decision everywhere it appears
if (fnType.id < 0 && queryElem.id < 0) {
- if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) {
- return false;
- }
- for (const [fid, qid] of mgens.entries()) {
- if (fnType.id !== fid && queryElem.id === qid) {
+ if (mgensIn) {
+ if (mgensIn.has(fnType.id) && mgensIn.get(fnType.id) !== queryElem.id) {
return false;
}
- if (fnType.id === fid && queryElem.id !== qid) {
- return false;
+ for (const [fid, qid] of mgensIn.entries()) {
+ if (fnType.id !== fid && queryElem.id === qid) {
+ return false;
+ }
+ if (fnType.id === fid && queryElem.id !== qid) {
+ return false;
+ }
}
}
+ return true;
} else {
if (queryElem.id === typeNameIdOfArrayOrSlice &&
(fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray)
) {
// [] matches primitive:array or primitive:slice
// if it matches, then we're fine, and this is an appropriate match candidate
- } else if (fnType.id !== queryElem.id) {
+ } else if (fnType.id !== queryElem.id || queryElem.id === null) {
return false;
}
// If the query elem has generics, and the function doesn't,
// it can't match.
- if (fnType.generics.length === 0 && queryElem.generics.length !== 0) {
+ if ((fnType.generics.length + fnType.bindings.size) === 0 &&
+ queryElem.generics.length !== 0
+ ) {
+ return false;
+ }
+ if (fnType.bindings.size < queryElem.bindings.size) {
return false;
}
// If the query element is a path (it contains `::`), we need to check if this
@@ -1595,9 +1638,87 @@ function initSearch(rawSearchIndex) {
return false;
}
}
+ return true;
+ }
+ }
+ /**
+ * This function checks the associated type bindings. Any that aren't matched get converted
+ * to generics, and this function returns an array of the function's generics with these
+ * simplified bindings added to them. That is, it takes a path like this:
+ *
+ * Iterator<Item=u32>
+ *
+ * ... if queryElem itself has an `Item=` in it, then this function returns an empty array.
+ * But if queryElem contains no Item=, then this function returns a one-item array with the
+ * ID of u32 in it, and the rest of the matching engine acts as if `Iterator<u32>` were
+ * the type instead.
+ *
+ * @param {FunctionType} fnType
+ * @param {QueryElement} queryElem
+ * @param {[FunctionType]} whereClause - Trait bounds for generic items.
+ * @param {Map<number,number>} mgensIn - Map functions generics to query generics.
+ * Never modified.
+ * @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}}
+ */
+ function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) {
+ if (fnType.bindings.size < queryElem.bindings.size) {
+ return false;
+ }
+ let simplifiedGenerics = fnType.generics || [];
+ if (fnType.bindings.size > 0) {
+ let mgensSolutionSet = [mgensIn];
+ for (const [name, constraints] of queryElem.bindings.entries()) {
+ if (mgensSolutionSet.length === 0) {
+ return false;
+ }
+ if (!fnType.bindings.has(name)) {
+ return false;
+ }
+ const fnTypeBindings = fnType.bindings.get(name);
+ mgensSolutionSet = mgensSolutionSet.flatMap(mgens => {
+ const newSolutions = [];
+ unifyFunctionTypes(
+ fnTypeBindings,
+ constraints,
+ whereClause,
+ mgens,
+ newMgens => {
+ newSolutions.push(newMgens);
+ // return `false` makes unifyFunctionTypes return the full set of
+ // possible solutions
+ return false;
+ }
+ );
+ return newSolutions;
+ });
+ }
+ if (mgensSolutionSet.length === 0) {
+ return false;
+ }
+ const binds = Array.from(fnType.bindings.entries()).flatMap(entry => {
+ const [name, constraints] = entry;
+ if (queryElem.bindings.has(name)) {
+ return [];
+ } else {
+ return constraints;
+ }
+ });
+ if (simplifiedGenerics.length > 0) {
+ simplifiedGenerics = [...simplifiedGenerics, ...binds];
+ } else {
+ simplifiedGenerics = binds;
+ }
+ return { simplifiedGenerics, mgens: mgensSolutionSet };
}
- return true;
+ return { simplifiedGenerics, mgens: [mgensIn] };
}
+ /**
+ * @param {FunctionType} fnType
+ * @param {QueryElement} queryElem
+ * @param {[FunctionType]} whereClause - Trait bounds for generic items.
+ * @param {Map<number,number>|null} mgens - Map functions generics to query generics.
+ * @returns {boolean}
+ */
function unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens) {
if (fnType.id < 0 && queryElem.id >= 0) {
if (!whereClause) {
@@ -1605,16 +1726,29 @@ function initSearch(rawSearchIndex) {
}
// mgens[fnType.id] === 0 indicates that we committed to unboxing this generic
// mgens[fnType.id] === null indicates that we haven't decided yet
- if (mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
+ if (mgens && mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
return false;
}
+ // Where clauses can represent cyclical data.
+ // `null` prevents it from trying to unbox in an infinite loop
+ const mgensTmp = new Map(mgens);
+ mgensTmp.set(fnType.id, null);
// This is only a potential unbox if the search query appears in the where clause
// for example, searching `Read -> usize` should find
// `fn read_all<R: Read>(R) -> Result<usize>`
// generic `R` is considered "unboxed"
- return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause);
- } else if (fnType.generics && fnType.generics.length > 0) {
- return checkIfInList(fnType.generics, queryElem, whereClause);
+ return checkIfInList(
+ whereClause[(-fnType.id) - 1],
+ queryElem,
+ whereClause,
+ mgensTmp
+ );
+ } else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
+ const simplifiedGenerics = [
+ ...fnType.generics,
+ ...Array.from(fnType.bindings.values()).flat(),
+ ];
+ return checkIfInList(simplifiedGenerics, queryElem, whereClause, mgens);
}
return false;
}
@@ -1626,12 +1760,13 @@ function initSearch(rawSearchIndex) {
* @param {Array<FunctionType>} list
* @param {QueryElement} elem - The element from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
+ * @param {Map<number,number>|null} mgens - Map functions generics to query generics.
*
* @return {boolean} - Returns true if found, false otherwise.
*/
- function checkIfInList(list, elem, whereClause) {
+ function checkIfInList(list, elem, whereClause, mgens) {
for (const entry of list) {
- if (checkType(entry, elem, whereClause)) {
+ if (checkType(entry, elem, whereClause, mgens)) {
return true;
}
}
@@ -1645,42 +1780,29 @@ function initSearch(rawSearchIndex) {
* @param {Row} row
* @param {QueryElement} elem - The element from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
+ * @param {Map<number,number>|null} mgens - Map functions generics to query generics.
*
* @return {boolean} - Returns true if the type matches, false otherwise.
*/
- function checkType(row, elem, whereClause) {
- if (row.id === null) {
- // This is a pure "generic" search, no need to run other checks.
- return row.generics.length > 0
- ? checkIfInList(row.generics, elem, whereClause)
- : false;
- }
-
- if (row.id < 0 && elem.id >= 0) {
- const gid = (-row.id) - 1;
- return checkIfInList(whereClause[gid], elem, whereClause);
- }
-
- if (row.id < 0 && elem.id < 0) {
- return true;
- }
-
- const matchesExact = row.id === elem.id;
- const matchesArrayOrSlice = elem.id === typeNameIdOfArrayOrSlice &&
- (row.id === typeNameIdOfSlice || row.id === typeNameIdOfArray);
-
- if ((matchesExact || matchesArrayOrSlice) &&
- typePassesFilter(elem.typeFilter, row.ty)) {
- if (elem.generics.length > 0) {
- return checkGenerics(row, elem, whereClause, new Map());
+ function checkType(row, elem, whereClause, mgens) {
+ if (row.bindings.size === 0 && elem.bindings.size === 0) {
+ if (elem.id < 0) {
+ return row.id < 0 || checkIfInList(row.generics, elem, whereClause, mgens);
+ }
+ if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
+ typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
+ // special case
+ elem.id !== typeNameIdOfArrayOrSlice
+ ) {
+ return row.id === elem.id || checkIfInList(
+ row.generics,
+ elem,
+ whereClause,
+ mgens
+ );
}
- return true;
}
-
- // 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 checkIfInList(row.generics, elem, whereClause);
+ return unifyFunctionTypes([row], [elem], whereClause, mgens);
}
function checkPath(contains, ty, maxEditDistance) {
@@ -1696,26 +1818,16 @@ function initSearch(rawSearchIndex) {
const length = path.length;
const clength = contains.length;
- if (clength > length) {
- return maxEditDistance + 1;
- }
- for (let i = 0; i < length; ++i) {
- if (i + clength > length) {
- break;
- }
+ pathiter: for (let i = length - clength; i >= 0; i -= 1) {
let dist_total = 0;
- let aborted = false;
for (let x = 0; x < clength; ++x) {
const dist = editDistance(path[i + x], contains[x], maxEditDistance);
if (dist > maxEditDistance) {
- aborted = true;
- break;
+ continue pathiter;
}
dist_total += dist;
}
- if (!aborted) {
- ret_dist = Math.min(ret_dist, Math.round(dist_total / clength));
- }
+ ret_dist = Math.min(ret_dist, Math.round(dist_total / clength));
}
return ret_dist;
}
@@ -1819,7 +1931,7 @@ function initSearch(rawSearchIndex) {
* The `results` map contains information which will be used to sort the search results:
*
* * `fullId` is a `string`` used as the key of the object we use for the `results` map.
- * * `id` is the index in both `searchWords` and `searchIndex` arrays for this element.
+ * * `id` is the index in the `searchIndex` array for this element.
* * `index` is an `integer`` used to sort by the position of the word in the item's name.
* * `dist` is the main metric used to sort the search results.
* * `path_dist` is zero if a single-component search query is used, otherwise it's the
@@ -1833,8 +1945,7 @@ function initSearch(rawSearchIndex) {
* @param {integer} path_dist
*/
function addIntoResults(results, fullId, id, index, dist, path_dist, maxEditDistance) {
- const inBounds = dist <= maxEditDistance || index !== -1;
- if (dist === 0 || (!parsedQuery.literalSearch && inBounds)) {
+ if (dist <= maxEditDistance || index !== -1) {
if (results.has(fullId)) {
const result = results.get(fullId);
if (result.dontValidate || result.dist <= dist) {
@@ -1878,40 +1989,44 @@ function initSearch(rawSearchIndex) {
if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
return;
}
- let index = -1, path_dist = 0;
+ let path_dist = 0;
const fullId = row.id;
- const searchWord = searchWords[pos];
- const in_args = row.type && row.type.inputs
- && checkIfInList(row.type.inputs, elem, row.type.where_clause);
- if (in_args) {
- // 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 = row.type && row.type.output
- && checkIfInList(row.type.output, elem, row.type.where_clause);
- if (returned) {
- addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance);
+ // fpDist is a minimum possible type distance, where "type distance" is the number of
+ // atoms in the function not present in the query
+ const tfpDist = compareTypeFingerprints(
+ fullId,
+ parsedQuery.typeFingerprint
+ );
+ if (tfpDist !== null) {
+ const in_args = row.type && row.type.inputs
+ && checkIfInList(row.type.inputs, elem, row.type.where_clause);
+ const returned = row.type && row.type.output
+ && checkIfInList(row.type.output, elem, row.type.where_clause);
+ if (in_args) {
+ results_in_args.max_dist = Math.max(results_in_args.max_dist || 0, tfpDist);
+ const maxDist = results_in_args.size < MAX_RESULTS ?
+ (tfpDist + 1) :
+ results_in_args.max_dist;
+ addIntoResults(results_in_args, fullId, pos, -1, tfpDist, 0, maxDist);
+ }
+ if (returned) {
+ results_returned.max_dist = Math.max(results_returned.max_dist || 0, tfpDist);
+ const maxDist = results_returned.size < MAX_RESULTS ?
+ (tfpDist + 1) :
+ results_returned.max_dist;
+ addIntoResults(results_returned, fullId, pos, -1, tfpDist, 0, maxDist);
+ }
}
if (!typePassesFilter(elem.typeFilter, row.ty)) {
return;
}
- const row_index = row.normalizedName.indexOf(elem.pathLast);
- const word_index = searchWord.indexOf(elem.pathLast);
-
- // lower indexes are "better" matches
- // rank based on the "best" match
- if (row_index === -1) {
- index = word_index;
- } else if (word_index === -1) {
- index = row_index;
- } else if (word_index < row_index) {
- index = word_index;
- } else {
- index = row_index;
+ let index = row.word.indexOf(elem.pathLast);
+ const normalizedIndex = row.normalizedName.indexOf(elem.pathLast);
+ if (index === -1 || (index > normalizedIndex && normalizedIndex !== -1)) {
+ index = normalizedIndex;
}
if (elem.fullPath.length > 1) {
@@ -1922,13 +2037,13 @@ function initSearch(rawSearchIndex) {
}
if (parsedQuery.literalSearch) {
- if (searchWord === elem.name) {
+ if (row.word === elem.pathLast) {
addIntoResults(results_others, fullId, pos, index, 0, path_dist);
}
return;
}
- const dist = editDistance(searchWord, elem.pathLast, maxEditDistance);
+ const dist = editDistance(row.normalizedName, elem.normalizedPathLast, maxEditDistance);
if (index === -1 && dist + path_dist > maxEditDistance) {
return;
@@ -1951,6 +2066,17 @@ function initSearch(rawSearchIndex) {
return;
}
+ const tfpDist = compareTypeFingerprints(
+ row.id,
+ parsedQuery.typeFingerprint
+ );
+ if (tfpDist === null) {
+ return;
+ }
+ if (results.size >= MAX_RESULTS && tfpDist > results.max_dist) {
+ return;
+ }
+
// If the result is too "bad", we return false and it ends this search.
if (!unifyFunctionTypes(
row.type.inputs,
@@ -1969,12 +2095,11 @@ function initSearch(rawSearchIndex) {
return;
}
- addIntoResults(results, row.id, pos, 0, 0, 0, Number.MAX_VALUE);
+ results.max_dist = Math.max(results.max_dist || 0, tfpDist);
+ addIntoResults(results, row.id, pos, 0, tfpDist, 0, Number.MAX_VALUE);
}
function innerRunQuery() {
- let elem, i, nSearchWords, in_returned, row;
-
let queryLen = 0;
for (const elem of parsedQuery.elems) {
queryLen += elem.name.length;
@@ -2000,17 +2125,20 @@ function initSearch(rawSearchIndex) {
* See `buildTypeMapIndex` for more information.
*
* @param {QueryElement} elem
+ * @param {boolean} isAssocType
*/
- function convertNameToId(elem) {
- if (typeNameIdMap.has(elem.pathLast)) {
- elem.id = typeNameIdMap.get(elem.pathLast);
+ function convertNameToId(elem, isAssocType) {
+ if (typeNameIdMap.has(elem.normalizedPathLast) &&
+ (isAssocType || !typeNameIdMap.get(elem.normalizedPathLast).assocOnly)) {
+ elem.id = typeNameIdMap.get(elem.normalizedPathLast).id;
} else if (!parsedQuery.literalSearch) {
let match = null;
let matchDist = maxEditDistance + 1;
let matchName = "";
- for (const [name, id] of typeNameIdMap) {
- const dist = editDistance(name, elem.pathLast, maxEditDistance);
- if (dist <= matchDist && dist <= maxEditDistance) {
+ for (const [name, {id, assocOnly}] of typeNameIdMap) {
+ const dist = editDistance(name, elem.normalizedPathLast, maxEditDistance);
+ if (dist <= matchDist && dist <= maxEditDistance &&
+ (isAssocType || !assocOnly)) {
if (dist === matchDist && matchName > name) {
continue;
}
@@ -2025,7 +2153,7 @@ function initSearch(rawSearchIndex) {
elem.id = match;
}
if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1
- && elem.generics.length === 0)
+ && elem.generics.length === 0 && elem.bindings.size === 0)
|| elem.typeFilter === TY_GENERIC) {
if (genericSymbols.has(elem.name)) {
elem.id = genericSymbols.get(elem.name);
@@ -2068,19 +2196,40 @@ function initSearch(rawSearchIndex) {
for (const elem2 of elem.generics) {
convertNameToId(elem2);
}
+ elem.bindings = new Map(Array.from(elem.bindings.entries())
+ .map(entry => {
+ const [name, constraints] = entry;
+ if (!typeNameIdMap.has(name)) {
+ parsedQuery.error = [
+ "Type parameter ",
+ name,
+ " does not exist",
+ ];
+ return [null, []];
+ }
+ for (const elem2 of constraints) {
+ convertNameToId(elem2);
+ }
+
+ return [typeNameIdMap.get(name).id, constraints];
+ })
+ );
}
+ const fps = new Set();
for (const elem of parsedQuery.elems) {
convertNameToId(elem);
+ buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint, fps);
}
for (const elem of parsedQuery.returned) {
convertNameToId(elem);
+ buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint, fps);
}
- if (parsedQuery.foundElems === 1) {
+ if (parsedQuery.foundElems === 1 && parsedQuery.returned.length === 0) {
if (parsedQuery.elems.length === 1) {
- elem = parsedQuery.elems[0];
- for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
+ const elem = parsedQuery.elems[0];
+ for (let i = 0, nSearchIndex = searchIndex.length; i < nSearchIndex; ++i) {
// It means we want to check for this element everywhere (in names, args and
// returned).
handleSingleArg(
@@ -2093,30 +2242,25 @@ function initSearch(rawSearchIndex) {
maxEditDistance
);
}
- } else if (parsedQuery.returned.length === 1) {
- // We received one returned argument to check, so looking into returned values.
- elem = parsedQuery.returned[0];
- for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
- row = searchIndex[i];
- in_returned = row.type && unifyFunctionTypes(
- row.type.output,
- parsedQuery.returned,
- row.type.where_clause
- );
- if (in_returned) {
- 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) {
+ // Sort input and output so that generic type variables go first and
+ // types with generic parameters go last.
+ // That's because of the way unification is structured: it eats off
+ // the end, and hits a fast path if the last item is a simple atom.
+ const sortQ = (a, b) => {
+ const ag = a.generics.length === 0 && a.bindings.size === 0;
+ const bg = b.generics.length === 0 && b.bindings.size === 0;
+ if (ag !== bg) {
+ return ag - bg;
+ }
+ const ai = a.id > 0;
+ const bi = b.id > 0;
+ return ai - bi;
+ };
+ parsedQuery.elems.sort(sortQ);
+ parsedQuery.returned.sort(sortQ);
+ for (let i = 0, nSearchIndex = searchIndex.length; i < nSearchIndex; ++i) {
handleArgs(searchIndex[i], i, results_others);
}
}
@@ -2139,44 +2283,6 @@ function initSearch(rawSearchIndex) {
return ret;
}
- /**
- * Validate performs the following boolean logic. For example:
- * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
- * exists in (name || path || parent) OR => ("file" && "open") exists in
- * (name || path )
- *
- * This could be written functionally, but I wanted to minimise
- * functions on stack.
- *
- * @param {string} name - The name of the result
- * @param {string} path - The path of the result
- * @param {string} keys - The keys to be used (["file", "open"])
- * @param {Object} parent - The parent of the result
- *
- * @return {boolean} - Whether the result is valid or not
- */
- function validateResult(name, path, keys, parent, maxEditDistance) {
- if (!keys || !keys.length) {
- return true;
- }
- for (const key of keys) {
- // each check is for validation so we negate the conditions and invalidate
- if (!(
- // check for an exact name match
- name.indexOf(key) > -1 ||
- // then an exact path match
- path.indexOf(key) > -1 ||
- // next if there is a parent, check for exact parent match
- (parent !== undefined && parent.name !== undefined &&
- parent.name.toLowerCase().indexOf(key) > -1) ||
- // lastly check to see if the name was an editDistance match
- editDistance(name, key, maxEditDistance) <= maxEditDistance)) {
- return false;
- }
- }
- return true;
- }
-
function nextTab(direction) {
const next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length;
searchState.focusedByTab[searchState.currentTab] = document.activeElement;
@@ -2269,13 +2375,9 @@ function initSearch(rawSearchIndex) {
* @param {boolean} display - True if this is the active tab
*/
function addTab(array, query, display) {
- let extraClass = "";
- if (display === true) {
- extraClass = " active";
- }
+ const extraClass = display ? " active" : "";
const output = document.createElement("div");
- let length = 0;
if (array.length > 0) {
output.className = "search-results " + extraClass;
@@ -2285,8 +2387,6 @@ function initSearch(rawSearchIndex) {
const longType = longItemTypes[item.ty];
const typeName = longType.length !== 0 ? `${longType}` : "?";
- length += 1;
-
const link = document.createElement("a");
link.className = "result-" + type;
link.href = item.href;
@@ -2334,7 +2434,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
"href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
" <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
}
- return [output, length];
+ return [output, array.length];
}
function makeTabHeader(tabNb, text, nbElems) {
@@ -2413,11 +2513,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
}
let crates = "";
- const crates_list = Object.keys(rawSearchIndex);
- if (crates_list.length > 1) {
+ if (rawSearchIndex.size > 1) {
crates = " in&nbsp;<div id=\"crate-search-div\"><select id=\"crate-search\">" +
"<option value=\"all crates\">all crates</option>";
- for (const c of crates_list) {
+ for (const c of rawSearchIndex.keys()) {
crates += `<option value="${c}" ${c === filterCrates && "selected"}>${c}</option>`;
}
crates += "</select></div>";
@@ -2514,13 +2613,9 @@ ${item.displayPath}<span class="${type}">${name}</span>\
/**
* Perform a search based on the current state of the search input element
* and display the results.
- * @param {Event} [e] - The event that triggered this search, if any
* @param {boolean} [forced]
*/
- function search(e, forced) {
- if (e) {
- e.preventDefault();
- }
+ function search(forced) {
const query = parseQuery(searchState.input.value.trim());
let filterCrates = getFilterCrates();
@@ -2549,7 +2644,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
updateSearchHistory(buildUrl(query.original, filterCrates));
showResults(
- execQuery(query, searchWords, filterCrates, window.currentCrate),
+ execQuery(query, filterCrates, window.currentCrate),
params.go_to_first,
filterCrates);
}
@@ -2581,19 +2676,42 @@ ${item.displayPath}<span class="${type}">${name}</span>\
*
* @param {RawFunctionType} type
*/
- function buildItemSearchType(type, lowercasePaths) {
+ function buildItemSearchType(type, lowercasePaths, isAssocType) {
const PATH_INDEX_DATA = 0;
const GENERICS_DATA = 1;
- let pathIndex, generics;
+ const BINDINGS_DATA = 2;
+ let pathIndex, generics, bindings;
if (typeof type === "number") {
pathIndex = type;
generics = [];
+ bindings = new Map();
} else {
pathIndex = type[PATH_INDEX_DATA];
generics = buildItemSearchTypeAll(
type[GENERICS_DATA],
lowercasePaths
);
+ if (type.length > BINDINGS_DATA) {
+ bindings = new Map(type[BINDINGS_DATA].map(binding => {
+ const [assocType, constraints] = binding;
+ // Associated type constructors are represented sloppily in rustdoc's
+ // type search, to make the engine simpler.
+ //
+ // MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T>
+ // and both are, essentially
+ // MyType<Output=(T, Result<T>)>, except the tuple isn't actually there.
+ // It's more like the value of a type binding is naturally an array,
+ // which rustdoc calls "constraints".
+ //
+ // As a result, the key should never have generics on it.
+ return [
+ buildItemSearchType(assocType, lowercasePaths, true).id,
+ buildItemSearchTypeAll(constraints, lowercasePaths),
+ ];
+ }));
+ } else {
+ bindings = new Map();
+ }
}
if (pathIndex < 0) {
// types less than 0 are generic parameters
@@ -2603,6 +2721,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
ty: TY_GENERIC,
path: null,
generics,
+ bindings,
};
}
if (pathIndex === 0) {
@@ -2612,14 +2731,16 @@ ${item.displayPath}<span class="${type}">${name}</span>\
ty: null,
path: null,
generics,
+ bindings,
};
}
const item = lowercasePaths[pathIndex - 1];
return {
- id: buildTypeMapIndex(item.name),
+ id: buildTypeMapIndex(item.name, isAssocType),
ty: item.ty,
path: item.path,
generics,
+ bindings,
};
}
@@ -2679,14 +2800,119 @@ ${item.displayPath}<span class="${type}">${name}</span>\
};
}
+ /**
+ * Type fingerprints allow fast, approximate matching of types.
+ *
+ * This algo creates a compact representation of the type set using a Bloom filter.
+ * This fingerprint is used three ways:
+ *
+ * - It accelerates the matching algorithm by checking the function fingerprint against the
+ * query fingerprint. If any bits are set in the query but not in the function, it can't
+ * match.
+ *
+ * - The fourth section has the number of distinct items in the set.
+ * This is the distance function, used for filtering and for sorting.
+ *
+ * [^1]: Distance is the relatively naive metric of counting the number of distinct items in
+ * the function that are not present in the query.
+ *
+ * @param {FunctionType|QueryElement} type - a single type
+ * @param {Uint32Array} output - write the fingerprint to this data structure: uses 128 bits
+ * @param {Set<number>} fps - Set of distinct items
+ */
+ function buildFunctionTypeFingerprint(type, output, fps) {
+ let input = type.id;
+ // All forms of `[]` get collapsed down to one thing in the bloom filter.
+ // Differentiating between arrays and slices, if the user asks for it, is
+ // still done in the matching algorithm.
+ if (input === typeNameIdOfArray || input === typeNameIdOfSlice) {
+ input = typeNameIdOfArrayOrSlice;
+ }
+ // http://burtleburtle.net/bob/hash/integer.html
+ // ~~ is toInt32. It's used before adding, so
+ // the number stays in safe integer range.
+ const hashint1 = k => {
+ k = (~~k + 0x7ed55d16) + (k << 12);
+ k = (k ^ 0xc761c23c) ^ (k >>> 19);
+ k = (~~k + 0x165667b1) + (k << 5);
+ k = (~~k + 0xd3a2646c) ^ (k << 9);
+ k = (~~k + 0xfd7046c5) + (k << 3);
+ return (k ^ 0xb55a4f09) ^ (k >>> 16);
+ };
+ const hashint2 = k => {
+ k = ~k + (k << 15);
+ k ^= k >>> 12;
+ k += k << 2;
+ k ^= k >>> 4;
+ k = Math.imul(k, 2057);
+ return k ^ (k >> 16);
+ };
+ if (input !== null) {
+ const h0a = hashint1(input);
+ const h0b = hashint2(input);
+ // Less Hashing, Same Performance: Building a Better Bloom Filter
+ // doi=10.1.1.72.2442
+ const h1a = ~~(h0a + Math.imul(h0b, 2));
+ const h1b = ~~(h0a + Math.imul(h0b, 3));
+ const h2a = ~~(h0a + Math.imul(h0b, 4));
+ const h2b = ~~(h0a + Math.imul(h0b, 5));
+ output[0] |= (1 << (h0a % 32)) | (1 << (h1b % 32));
+ output[1] |= (1 << (h1a % 32)) | (1 << (h2b % 32));
+ output[2] |= (1 << (h2a % 32)) | (1 << (h0b % 32));
+ fps.add(input);
+ }
+ for (const g of type.generics) {
+ buildFunctionTypeFingerprint(g, output, fps);
+ }
+ const fb = {
+ id: null,
+ ty: 0,
+ generics: [],
+ bindings: new Map(),
+ };
+ for (const [k, v] of type.bindings.entries()) {
+ fb.id = k;
+ fb.generics = v;
+ buildFunctionTypeFingerprint(fb, output, fps);
+ }
+ output[3] = fps.size;
+ }
+
+ /**
+ * Compare the query fingerprint with the function fingerprint.
+ *
+ * @param {{number}} fullId - The function
+ * @param {{Uint32Array}} queryFingerprint - The query
+ * @returns {number|null} - Null if non-match, number if distance
+ * This function might return 0!
+ */
+ function compareTypeFingerprints(fullId, queryFingerprint) {
+ const fh0 = functionTypeFingerprint[fullId * 4];
+ const fh1 = functionTypeFingerprint[(fullId * 4) + 1];
+ const fh2 = functionTypeFingerprint[(fullId * 4) + 2];
+ const [qh0, qh1, qh2] = queryFingerprint;
+ // Approximate set intersection with bloom filters.
+ // This can be larger than reality, not smaller, because hashes have
+ // the property that if they've got the same value, they hash to the
+ // same thing. False positives exist, but not false negatives.
+ const [in0, in1, in2] = [fh0 & qh0, fh1 & qh1, fh2 & qh2];
+ // Approximate the set of items in the query but not the function.
+ // This might be smaller than reality, but cannot be bigger.
+ //
+ // | in_ | qh_ | XOR | Meaning |
+ // | --- | --- | --- | ------------------------------------------------ |
+ // | 0 | 0 | 0 | Not present |
+ // | 1 | 0 | 1 | IMPOSSIBLE because `in_` is `fh_ & qh_` |
+ // | 1 | 1 | 0 | If one or both is false positive, false negative |
+ // | 0 | 1 | 1 | Since in_ has no false negatives, must be real |
+ if ((in0 ^ qh0) || (in1 ^ qh1) || (in2 ^ qh2)) {
+ return null;
+ }
+ return functionTypeFingerprint[(fullId * 4) + 3];
+ }
+
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;
@@ -2698,81 +2924,86 @@ ${item.displayPath}<span class="${type}">${name}</span>\
typeNameIdOfSlice = buildTypeMapIndex("slice");
typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
- for (const crate in rawSearchIndex) {
- if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
- continue;
- }
-
- let crateSize = 0;
-
- /**
- * The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f`
- * are arrays with the same length. `q`, `a`, and `c` use a sparse
- * representation for compactness.
- *
- * `n[i]` contains the name of an item.
- *
- * `t[i]` contains the type of that item
- * (as a string of characters that represent an offset in `itemTypes`).
- *
- * `d[i]` contains the description of that item.
- *
- * `q` contains the full paths of the items. For compactness, it is a set of
- * (index, path) pairs used to create a map. If a given index `i` is
- * not present, this indicates "same as the last index present".
- *
- * `i[i]` contains an item's parent, usually a module. For compactness,
- * it is a set of indexes into the `p` array.
- *
- * `f[i]` contains function signatures, or `0` if the item isn't a function.
- * Functions are themselves encoded as arrays. The first item is a list of
- * types representing the function's inputs, and the second list item is a list
- * of types representing the function's output. Tuples are flattened.
- * Types are also represented as arrays; the first item is an index into the `p`
- * array, while the second is a list of types representing any generic parameters.
- *
- * b[i] contains an item's impl disambiguator. This is only present if an item
- * is defined in an impl block and, the impl block's type has more than one associated
- * item with the same name.
- *
- * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
- * points into the n/t/d/q/i/f arrays.
- *
- * `doc` contains the description of the crate.
- *
- * `p` is a list of path/type pairs. It is used for parents and function parameters.
- *
- * `c` is an array of item indices that are deprecated.
- *
- * @type {{
- * doc: string,
- * a: Object,
- * n: Array<string>,
- * t: String,
- * d: Array<string>,
- * q: Array<[Number, string]>,
- * i: Array<Number>,
- * f: Array<RawFunctionSearchType>,
- * p: Array<Object>,
- * b: Array<[Number, String]>,
- * c: Array<Number>
- * }}
- */
- const crateCorpus = rawSearchIndex[crate];
+ // Function type fingerprints are 128-bit bloom filters that are used to
+ // estimate the distance between function and query.
+ // This loop counts the number of items to allocate a fingerprint for.
+ for (const crate of rawSearchIndex.values()) {
+ // Each item gets an entry in the fingerprint array, and the crate
+ // does, too
+ id += crate.t.length + 1;
+ }
+ functionTypeFingerprint = new Uint32Array((id + 1) * 4);
- searchWords.push(crate);
+ // This loop actually generates the search item indexes, including
+ // normalized names, type signature objects and fingerprints, and aliases.
+ id = 0;
+ /**
+ * The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f`
+ * are arrays with the same length. `q`, `a`, and `c` use a sparse
+ * representation for compactness.
+ *
+ * `n[i]` contains the name of an item.
+ *
+ * `t[i]` contains the type of that item
+ * (as a string of characters that represent an offset in `itemTypes`).
+ *
+ * `d[i]` contains the description of that item.
+ *
+ * `q` contains the full paths of the items. For compactness, it is a set of
+ * (index, path) pairs used to create a map. If a given index `i` is
+ * not present, this indicates "same as the last index present".
+ *
+ * `i[i]` contains an item's parent, usually a module. For compactness,
+ * it is a set of indexes into the `p` array.
+ *
+ * `f[i]` contains function signatures, or `0` if the item isn't a function.
+ * Functions are themselves encoded as arrays. The first item is a list of
+ * types representing the function's inputs, and the second list item is a list
+ * of types representing the function's output. Tuples are flattened.
+ * Types are also represented as arrays; the first item is an index into the `p`
+ * array, while the second is a list of types representing any generic parameters.
+ *
+ * b[i] contains an item's impl disambiguator. This is only present if an item
+ * is defined in an impl block and, the impl block's type has more than one associated
+ * item with the same name.
+ *
+ * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
+ * points into the n/t/d/q/i/f arrays.
+ *
+ * `doc` contains the description of the crate.
+ *
+ * `p` is a list of path/type pairs. It is used for parents and function parameters.
+ *
+ * `c` is an array of item indices that are deprecated.
+ *
+ * @type {{
+ * doc: string,
+ * a: Object,
+ * n: Array<string>,
+ * t: String,
+ * d: Array<string>,
+ * q: Array<[Number, string]>,
+ * i: Array<Number>,
+ * f: Array<RawFunctionSearchType>,
+ * p: Array<Object>,
+ * b: Array<[Number, String]>,
+ * c: Array<Number>
+ * }}
+ */
+ for (const [crate, crateCorpus] of rawSearchIndex) {
// This object should have exactly the same set of fields as the "row"
// object defined below. Your JavaScript runtime will thank you.
// https://mathiasbynens.be/notes/shapes-ics
const crateRow = {
crate: crate,
- ty: 1, // == ExternCrate
+ ty: 3, // == ExternCrate
name: crate,
path: "",
desc: crateCorpus.doc,
parent: undefined,
type: null,
id: id,
+ word: crate,
normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
deprecated: null,
implDisambiguator: null,
@@ -2840,13 +3071,34 @@ ${item.displayPath}<span class="${type}">${name}</span>\
len = itemTypes.length;
for (let i = 0; i < len; ++i) {
let word = "";
- // This object should have exactly the same set of fields as the "crateRow"
- // object defined above.
if (typeof itemNames[i] === "string") {
word = itemNames[i].toLowerCase();
}
- searchWords.push(word);
const path = itemPaths.has(i) ? itemPaths.get(i) : lastPath;
+ let type = null;
+ if (itemFunctionSearchTypes[i] !== 0) {
+ type = buildFunctionSearchType(
+ itemFunctionSearchTypes[i],
+ lowercasePaths
+ );
+ if (type) {
+ const fp = functionTypeFingerprint.subarray(id * 4, (id + 1) * 4);
+ const fps = new Set();
+ for (const t of type.inputs) {
+ buildFunctionTypeFingerprint(t, fp, fps);
+ }
+ for (const t of type.output) {
+ buildFunctionTypeFingerprint(t, fp, fps);
+ }
+ for (const w of type.where_clause) {
+ for (const t of w) {
+ buildFunctionTypeFingerprint(t, fp, fps);
+ }
+ }
+ }
+ }
+ // This object should have exactly the same set of fields as the "crateRow"
+ // object defined above.
const row = {
crate: crate,
ty: itemTypes.charCodeAt(i) - charA,
@@ -2854,11 +3106,9 @@ ${item.displayPath}<span class="${type}">${name}</span>\
path: path,
desc: itemDescs[i],
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
- type: buildFunctionSearchType(
- itemFunctionSearchTypes[i],
- lowercasePaths
- ),
+ type,
id: id,
+ word,
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
deprecated: deprecatedItems.has(i),
implDisambiguator: implDisambiguator.has(i) ? implDisambiguator.get(i) : null,
@@ -2866,14 +3116,13 @@ ${item.displayPath}<span class="${type}">${name}</span>\
id += 1;
searchIndex.push(row);
lastPath = row.path;
- crateSize += 1;
}
if (aliases) {
const currentCrateAliases = new Map();
ALIASES.set(crate, currentCrateAliases);
for (const alias_name in aliases) {
- if (!hasOwnPropertyRustdoc(aliases, alias_name)) {
+ if (!Object.prototype.hasOwnProperty.call(aliases, alias_name)) {
continue;
}
@@ -2889,9 +3138,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
}
}
}
- currentIndex += crateSize;
+ currentIndex += itemTypes.length;
}
- return searchWords;
}
/**
@@ -3031,7 +3279,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
// popping a state (Firefox), which is why search() is
// called both here and at the end of the startSearch()
// function.
- search(e);
+ e.preventDefault();
+ search();
} else {
searchState.input.value = "";
// When browsing back from search results the main page
@@ -3066,13 +3315,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
// before paste back the previous search, you get the old search results without
// the filter. To prevent this, we need to remove the previous results.
currentResults = null;
- search(undefined, true);
+ search(true);
}
- /**
- * @type {Array<string>}
- */
- const searchWords = buildIndex(rawSearchIndex);
+ buildIndex(rawSearchIndex);
if (typeof window !== "undefined") {
registerSearchEvents();
// If there's a search term in the URL, execute the search now.
@@ -3086,7 +3332,6 @@ ${item.displayPath}<span class="${type}">${name}</span>\
exports.execQuery = execQuery;
exports.parseQuery = parseQuery;
}
- return searchWords;
}
if (typeof window !== "undefined") {
@@ -3097,7 +3342,7 @@ if (typeof window !== "undefined") {
} else {
// Running in Node, not a browser. Run initSearch just to produce the
// exports.
- initSearch({});
+ initSearch(new Map());
}
diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js
index 63947789c..2b42fbebb 100644
--- a/src/librustdoc/html/static/js/settings.js
+++ b/src/librustdoc/html/static/js/settings.js
@@ -1,6 +1,6 @@
// Local js definitions:
/* global getSettingValue, updateLocalStorage, updateTheme */
-/* global addClass, removeClass, onEach, onEachLazy, blurHandler, elemIsInParent */
+/* global addClass, removeClass, onEach, onEachLazy, blurHandler */
/* global MAIN_ID, getVar, getSettingsButton */
"use strict";
@@ -29,6 +29,13 @@
window.rustdoc_remove_line_numbers_from_examples();
}
break;
+ case "hide-sidebar":
+ if (value === true) {
+ addClass(document.documentElement, "hide-sidebar");
+ } else {
+ removeClass(document.documentElement, "hide-sidebar");
+ }
+ break;
}
}
@@ -187,6 +194,11 @@
"default": false,
},
{
+ "name": "Hide persistent navigation bar",
+ "js_name": "hide-sidebar",
+ "default": false,
+ },
+ {
"name": "Disable keyboard shortcuts",
"js_name": "disable-shortcuts",
"default": false,
@@ -216,6 +228,13 @@
function displaySettings() {
settingsMenu.style.display = "";
+ onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"), el => {
+ const val = getSettingValue(el.id);
+ const checked = val === "true";
+ if (checked !== el.checked && val !== null) {
+ el.checked = checked;
+ }
+ });
}
function settingsBlurHandler(event) {
@@ -232,7 +251,7 @@
const settingsButton = getSettingsButton();
const settingsMenu = document.getElementById("settings");
settingsButton.onclick = event => {
- if (elemIsInParent(event.target, settingsMenu)) {
+ if (settingsMenu.contains(event.target)) {
return;
}
event.preventDefault();
diff --git a/src/librustdoc/html/static/js/src-script.js b/src/librustdoc/html/static/js/src-script.js
index 679c2341f..fc1d2d378 100644
--- a/src/librustdoc/html/static/js/src-script.js
+++ b/src/librustdoc/html/static/js/src-script.js
@@ -71,16 +71,31 @@ function createDirEntry(elem, parent, fullPath, hasFoundFile) {
return hasFoundFile;
}
+let toggleLabel;
+
+function getToggleLabel() {
+ toggleLabel = toggleLabel || document.querySelector("#src-sidebar-toggle button");
+ return toggleLabel;
+}
+
+window.rustdocCloseSourceSidebar = () => {
+ removeClass(document.documentElement, "src-sidebar-expanded");
+ getToggleLabel().innerText = ">";
+ updateLocalStorage("source-sidebar-show", "false");
+};
+
+window.rustdocShowSourceSidebar = () => {
+ addClass(document.documentElement, "src-sidebar-expanded");
+ getToggleLabel().innerText = "<";
+ updateLocalStorage("source-sidebar-show", "true");
+};
+
function toggleSidebar() {
const child = this.parentNode.children[0];
if (child.innerText === ">") {
- addClass(document.documentElement, "src-sidebar-expanded");
- child.innerText = "<";
- updateLocalStorage("source-sidebar-show", "true");
+ window.rustdocShowSourceSidebar();
} else {
- removeClass(document.documentElement, "src-sidebar-expanded");
- child.innerText = ">";
- updateLocalStorage("source-sidebar-show", "false");
+ window.rustdocCloseSourceSidebar();
}
}
@@ -118,10 +133,10 @@ function createSrcSidebar() {
title.className = "title";
title.innerText = "Files";
sidebar.appendChild(title);
- Object.keys(srcIndex).forEach(key => {
- srcIndex[key][NAME_OFFSET] = key;
- hasFoundFile = createDirEntry(srcIndex[key], sidebar, "", hasFoundFile);
- });
+ for (const [key, source] of srcIndex) {
+ source[NAME_OFFSET] = key;
+ hasFoundFile = createDirEntry(source, sidebar, "", hasFoundFile);
+ }
container.appendChild(sidebar);
// Focus on the current file in the source files sidebar.
@@ -131,12 +146,8 @@ function createSrcSidebar() {
}
}
-const lineNumbersRegex = /^#?(\d+)(?:-(\d+))?$/;
-
-function highlightSrcLines(match) {
- if (typeof match === "undefined") {
- match = window.location.hash.match(lineNumbersRegex);
- }
+function highlightSrcLines() {
+ const match = window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/);
if (!match) {
return;
}
@@ -218,12 +229,7 @@ const handleSrcHighlight = (function() {
};
}());
-window.addEventListener("hashchange", () => {
- const match = window.location.hash.match(lineNumbersRegex);
- if (match) {
- return highlightSrcLines(match);
- }
-});
+window.addEventListener("hashchange", highlightSrcLines);
onEachLazy(document.getElementsByClassName("src-line-numbers"), el => {
el.addEventListener("click", handleSrcHighlight);
diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js
index c69641092..ac9c6f377 100644
--- a/src/librustdoc/html/static/js/storage.js
+++ b/src/librustdoc/html/static/js/storage.js
@@ -51,22 +51,11 @@ function removeClass(elem, className) {
* Run a callback for every element of an Array.
* @param {Array<?>} arr - The array to iterate over
* @param {function(?)} func - The callback
- * @param {boolean} [reversed] - Whether to iterate in reverse
*/
-function onEach(arr, func, reversed) {
- if (arr && arr.length > 0) {
- if (reversed) {
- for (let i = arr.length - 1; i >= 0; --i) {
- if (func(arr[i])) {
- return true;
- }
- }
- } else {
- for (const elem of arr) {
- if (func(elem)) {
- return true;
- }
- }
+function onEach(arr, func) {
+ for (const elem of arr) {
+ if (func(elem)) {
+ return true;
}
}
return false;
@@ -80,14 +69,12 @@ function onEach(arr, func, reversed) {
* https://developer.mozilla.org/en-US/docs/Web/API/NodeList
* @param {NodeList<?>|HTMLCollection<?>} lazyArray - An array to iterate over
* @param {function(?)} func - The callback
- * @param {boolean} [reversed] - Whether to iterate in reverse
*/
// eslint-disable-next-line no-unused-vars
-function onEachLazy(lazyArray, func, reversed) {
+function onEachLazy(lazyArray, func) {
return onEach(
Array.prototype.slice.call(lazyArray),
- func,
- reversed);
+ func);
}
function updateLocalStorage(name, value) {
@@ -196,11 +183,38 @@ if (getSettingValue("use-system-theme") !== "false" && window.matchMedia) {
updateTheme();
+// Hide, show, and resize the sidebar at page load time
+//
+// This needs to be done here because this JS is render-blocking,
+// so that the sidebar doesn't "jump" after appearing on screen.
+// The user interaction to change this is set up in main.js.
if (getSettingValue("source-sidebar-show") === "true") {
// At this point in page load, `document.body` is not available yet.
// Set a class on the `<html>` element instead.
addClass(document.documentElement, "src-sidebar-expanded");
}
+if (getSettingValue("hide-sidebar") === "true") {
+ // At this point in page load, `document.body` is not available yet.
+ // Set a class on the `<html>` element instead.
+ addClass(document.documentElement, "hide-sidebar");
+}
+function updateSidebarWidth() {
+ const desktopSidebarWidth = getSettingValue("desktop-sidebar-width");
+ if (desktopSidebarWidth && desktopSidebarWidth !== "null") {
+ document.documentElement.style.setProperty(
+ "--desktop-sidebar-width",
+ desktopSidebarWidth + "px"
+ );
+ }
+ const srcSidebarWidth = getSettingValue("src-sidebar-width");
+ if (srcSidebarWidth && srcSidebarWidth !== "null") {
+ document.documentElement.style.setProperty(
+ "--src-sidebar-width",
+ srcSidebarWidth + "px"
+ );
+ }
+}
+updateSidebarWidth();
// If we navigate away (for example to a settings page), and then use the back or
// forward button to get back to a page, the theme may have changed in the meantime.
@@ -214,5 +228,6 @@ if (getSettingValue("source-sidebar-show") === "true") {
window.addEventListener("pageshow", ev => {
if (ev.persisted) {
setTimeout(updateTheme, 0);
+ setTimeout(updateSidebarWidth, 0);
}
});
diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html
index f6d2fa348..8db7986fa 100644
--- a/src/librustdoc/html/templates/item_union.html
+++ b/src/librustdoc/html/templates/item_union.html
@@ -4,13 +4,13 @@
</code></pre>
{{ self.document() | safe }}
{% if self.fields_iter().peek().is_some() %}
- <h2 id="fields" class="fields small-section-header"> {# #}
+ <h2 id="fields" class="fields section-header"> {# #}
Fields<a href="#fields" class="anchor">§</a> {# #}
</h2>
{% for (field, ty) in self.fields_iter() %}
{% let name = field.name.expect("union field name") %}
<span id="structfield.{{ name }}" {#+ #}
- class="{{ ItemType::StructField +}} small-section-header"> {# #}
+ class="{{ ItemType::StructField +}} section-header"> {# #}
<a href="#structfield.{{ name }}" class="anchor field">§</a> {# #}
<code>{{ name }}: {{+ self.print_ty(ty) | safe }}</code> {# #}
</span>
diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html
index 3f6147bb9..60ca5660c 100644
--- a/src/librustdoc/html/templates/page.html
+++ b/src/librustdoc/html/templates/page.html
@@ -114,6 +114,7 @@
{% endif %}
{{ sidebar|safe }}
</nav> {# #}
+ <div class="sidebar-resizer"></div>
<main> {# #}
{% if page.css_class != "src" %}<div class="width-limiter">{% endif %}
<nav class="sub"> {# #}
@@ -128,6 +129,11 @@
{% endif %}
<form class="search-form"> {# #}
<span></span> {# This empty span is a hacky fix for Safari - See #93184 #}
+ {% if page.css_class != "src" %}
+ <div id="sidebar-button" tabindex="-1"> {# #}
+ <a href="{{page.root_path|safe}}{{layout.krate|safe}}/all.html" title="show sidebar"></a> {# #}
+ </div> {# #}
+ {% endif %}
<input {#+ #}
class="search-input" {#+ #}
name="search" {#+ #}
@@ -136,8 +142,8 @@
spellcheck="false" {#+ #}
placeholder="Click or press ‘S’ to search, ‘?’ for more options…" {#+ #}
type="search"> {# #}
- <div id="help-button" title="help" tabindex="-1"> {# #}
- <a href="{{page.root_path|safe}}help.html">?</a> {# #}
+ <div id="help-button" tabindex="-1"> {# #}
+ <a href="{{page.root_path|safe}}help.html" title="help">?</a> {# #}
</div> {# #}
<div id="settings-menu" tabindex="-1"> {# #}
<a href="{{page.root_path|safe}}settings.html" title="settings"> {# #}
diff --git a/src/librustdoc/html/templates/type_layout.html b/src/librustdoc/html/templates/type_layout.html
index b8b7785a2..e0516bb42 100644
--- a/src/librustdoc/html/templates/type_layout.html
+++ b/src/librustdoc/html/templates/type_layout.html
@@ -1,4 +1,4 @@
-<h2 id="layout" class="small-section-header"> {# #}
+<h2 id="layout" class="section-header"> {# #}
Layout<a href="#layout" class="anchor">§</a> {# #}
</h2> {# #}
<div class="docblock"> {# #}
@@ -54,7 +54,7 @@
<strong>Note:</strong> Encountered an error during type layout; {#+ #}
the type failed to be normalized. {# #}
</p> {# #}
- {% when Err(LayoutError::Cycle) %}
+ {% 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. {# #}