summaryrefslogtreecommitdiffstats
path: root/src/librustdoc
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc')
-rw-r--r--src/librustdoc/Cargo.toml6
-rw-r--r--src/librustdoc/askama.toml1
-rw-r--r--src/librustdoc/clean/blanket_impl.rs5
-rw-r--r--src/librustdoc/clean/cfg.rs1
-rw-r--r--src/librustdoc/clean/inline.rs106
-rw-r--r--src/librustdoc/clean/mod.rs288
-rw-r--r--src/librustdoc/clean/simplify.rs6
-rw-r--r--src/librustdoc/clean/types.rs218
-rw-r--r--src/librustdoc/clean/types/tests.rs13
-rw-r--r--src/librustdoc/clean/utils.rs6
-rw-r--r--src/librustdoc/config.rs22
-rw-r--r--src/librustdoc/core.rs12
-rw-r--r--src/librustdoc/docfs.rs28
-rw-r--r--src/librustdoc/doctest.rs17
-rw-r--r--src/librustdoc/formats/cache.rs5
-rw-r--r--src/librustdoc/html/format.rs202
-rw-r--r--src/librustdoc/html/highlight.rs63
-rw-r--r--src/librustdoc/html/markdown.rs15
-rw-r--r--src/librustdoc/html/render/context.rs57
-rw-r--r--src/librustdoc/html/render/mod.rs1092
-rw-r--r--src/librustdoc/html/render/print_item.rs806
-rw-r--r--src/librustdoc/html/render/search_index.rs68
-rw-r--r--src/librustdoc/html/render/sidebar.rs558
-rw-r--r--src/librustdoc/html/render/span_map.rs6
-rw-r--r--src/librustdoc/html/sources.rs64
-rw-r--r--src/librustdoc/html/static/COPYRIGHT.txt4
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css81
-rw-r--r--src/librustdoc/html/static/css/settings.css3
-rw-r--r--src/librustdoc/html/static/css/themes/ayu.css1
-rw-r--r--src/librustdoc/html/static/css/themes/dark.css1
-rw-r--r--src/librustdoc/html/static/css/themes/light.css1
-rw-r--r--src/librustdoc/html/static/fonts/FiraSans-LICENSE.txt4
-rw-r--r--src/librustdoc/html/static/fonts/NanumBarunGothic-LICENSE.txt4
-rw-r--r--src/librustdoc/html/static/fonts/SourceCodePro-LICENSE.txt4
-rw-r--r--src/librustdoc/html/static/fonts/SourceSerif4-LICENSE.md5
-rw-r--r--src/librustdoc/html/static/js/externs.js7
-rw-r--r--src/librustdoc/html/static/js/main.js115
-rw-r--r--src/librustdoc/html/static/js/search.js716
-rw-r--r--src/librustdoc/html/static/js/settings.js8
-rw-r--r--src/librustdoc/html/static/js/source-script.js9
-rw-r--r--src/librustdoc/html/static/js/storage.js127
-rw-r--r--src/librustdoc/html/templates/STYLE.md39
-rw-r--r--src/librustdoc/html/templates/item_info.html7
-rw-r--r--src/librustdoc/html/templates/item_union.html23
-rw-r--r--src/librustdoc/html/templates/page.html297
-rw-r--r--src/librustdoc/html/templates/print_item.html52
-rw-r--r--src/librustdoc/html/templates/short_item_info.html23
-rw-r--r--src/librustdoc/html/templates/sidebar.html37
-rw-r--r--src/librustdoc/html/templates/source.html21
-rw-r--r--src/librustdoc/json/conversions.rs39
-rw-r--r--src/librustdoc/json/mod.rs2
-rw-r--r--src/librustdoc/lib.rs9
-rw-r--r--src/librustdoc/passes/calculate_doc_coverage.rs1
-rw-r--r--src/librustdoc/passes/check_doc_test_visibility.rs1
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs570
-rw-r--r--src/librustdoc/passes/collect_trait_impls.rs8
-rw-r--r--src/librustdoc/passes/propagate_doc_cfg.rs4
-rw-r--r--src/librustdoc/passes/strip_hidden.rs9
-rw-r--r--src/librustdoc/visit_ast.rs33
59 files changed, 3184 insertions, 2746 deletions
diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml
index c48f7998c..29912b957 100644
--- a/src/librustdoc/Cargo.toml
+++ b/src/librustdoc/Cargo.toml
@@ -8,7 +8,7 @@ path = "lib.rs"
[dependencies]
arrayvec = { version = "0.7", default-features = false }
-askama = { version = "0.11", default-features = false, features = ["config"] }
+askama = { version = "0.12", default-features = false, features = ["config"] }
itertools = "0.10.1"
minifier = "0.2.2"
once_cell = "1.10.0"
@@ -20,15 +20,13 @@ smallvec = "1.8.1"
tempfile = "3"
tracing = "0.1"
tracing-tree = "0.2.0"
+threadpool = "1.8.1"
[dependencies.tracing-subscriber]
version = "0.3.3"
default-features = false
features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"]
-[target.'cfg(windows)'.dependencies]
-rayon = "1.5.1"
-
[dev-dependencies]
expect-test = "1.4.0"
diff --git a/src/librustdoc/askama.toml b/src/librustdoc/askama.toml
index 0c984f637..2732c4bc6 100644
--- a/src/librustdoc/askama.toml
+++ b/src/librustdoc/askama.toml
@@ -1,2 +1,3 @@
[general]
dirs = ["html/templates"]
+whitespace = "suppress"
diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs
index bcdbbcacc..3a3bf6a7a 100644
--- a/src/librustdoc/clean/blanket_impl.rs
+++ b/src/librustdoc/clean/blanket_impl.rs
@@ -1,6 +1,6 @@
use crate::rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use rustc_hir as hir;
-use rustc_infer::infer::{InferOk, TyCtxtInferExt};
+use rustc_infer::infer::{DefineOpaqueTypes, InferOk, TyCtxtInferExt};
use rustc_infer::traits;
use rustc_middle::ty::ToPredicate;
use rustc_span::DUMMY_SP;
@@ -47,8 +47,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
// Require the type the impl is implemented on to match
// our type, and ignore the impl if there was a mismatch.
- let cause = traits::ObligationCause::dummy();
- let Ok(eq_result) = infcx.at(&cause, param_env).eq(impl_trait_ref.self_ty(), impl_ty) else {
+ let Ok(eq_result) = infcx.at(&traits::ObligationCause::dummy(), param_env).eq(DefineOpaqueTypes::No, impl_trait_ref.self_ty(), impl_ty) else {
continue
};
let InferOk { value: (), obligations } = eq_result;
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
index dd58a5b51..5177cffe6 100644
--- a/src/librustdoc/clean/cfg.rs
+++ b/src/librustdoc/clean/cfg.rs
@@ -517,6 +517,7 @@ impl<'a> fmt::Display for Display<'a> {
"aarch64" => "AArch64",
"arm" => "ARM",
"asmjs" => "JavaScript",
+ "loongarch64" => "LoongArch LA64",
"m68k" => "M68k",
"mips" => "MIPS",
"mips64" => "MIPS-64",
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 148243683..cc5d13808 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -36,15 +36,11 @@ use crate::formats::item_type::ItemType;
///
/// The returned value is `None` if the definition could not be inlined,
/// and `Some` of a vector of items if it was successfully expanded.
-///
-/// `parent_module` refers to the parent of the *re-export*, not the original item.
pub(crate) fn try_inline(
cx: &mut DocContext<'_>,
- parent_module: DefId,
- import_def_id: Option<DefId>,
res: Res,
name: Symbol,
- attrs: Option<&[ast::Attribute]>,
+ attrs: Option<(&[ast::Attribute], Option<DefId>)>,
visited: &mut DefIdSet,
) -> Option<Vec<clean::Item>> {
let did = res.opt_def_id()?;
@@ -55,38 +51,17 @@ pub(crate) fn try_inline(
debug!("attrs={:?}", attrs);
- let attrs_without_docs = attrs.map(|attrs| {
- attrs.into_iter().filter(|a| a.doc_str().is_none()).cloned().collect::<Vec<_>>()
+ let attrs_without_docs = attrs.map(|(attrs, def_id)| {
+ (attrs.into_iter().filter(|a| a.doc_str().is_none()).cloned().collect::<Vec<_>>(), def_id)
});
- // We need this ugly code because:
- //
- // ```
- // attrs_without_docs.map(|a| a.as_slice())
- // ```
- //
- // will fail because it returns a temporary slice and:
- //
- // ```
- // attrs_without_docs.map(|s| {
- // vec = s.as_slice();
- // vec
- // })
- // ```
- //
- // will fail because we're moving an uninitialized variable into a closure.
- let vec;
- let attrs_without_docs = match attrs_without_docs {
- Some(s) => {
- vec = s;
- Some(vec.as_slice())
- }
- None => None,
- };
+ let attrs_without_docs =
+ attrs_without_docs.as_ref().map(|(attrs, def_id)| (&attrs[..], *def_id));
+ let import_def_id = attrs.and_then(|(_, def_id)| def_id);
let kind = match res {
Res::Def(DefKind::Trait, did) => {
record_extern_fqn(cx, did, ItemType::Trait);
- build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
+ build_impls(cx, did, attrs_without_docs, &mut ret);
clean::TraitItem(Box::new(build_external_trait(cx, did)))
}
Res::Def(DefKind::Fn, did) => {
@@ -95,27 +70,27 @@ pub(crate) fn try_inline(
}
Res::Def(DefKind::Struct, did) => {
record_extern_fqn(cx, did, ItemType::Struct);
- build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
+ build_impls(cx, did, attrs_without_docs, &mut ret);
clean::StructItem(build_struct(cx, did))
}
Res::Def(DefKind::Union, did) => {
record_extern_fqn(cx, did, ItemType::Union);
- build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
+ build_impls(cx, did, attrs_without_docs, &mut ret);
clean::UnionItem(build_union(cx, did))
}
Res::Def(DefKind::TyAlias, did) => {
record_extern_fqn(cx, did, ItemType::Typedef);
- build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
+ build_impls(cx, did, attrs_without_docs, &mut ret);
clean::TypedefItem(build_type_alias(cx, did))
}
Res::Def(DefKind::Enum, did) => {
record_extern_fqn(cx, did, ItemType::Enum);
- build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
+ build_impls(cx, did, attrs_without_docs, &mut ret);
clean::EnumItem(build_enum(cx, did))
}
Res::Def(DefKind::ForeignTy, did) => {
record_extern_fqn(cx, did, ItemType::ForeignType);
- build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
+ build_impls(cx, did, attrs_without_docs, &mut ret);
clean::ForeignTypeItem
}
// Never inline enum variants but leave them shown as re-exports.
@@ -136,7 +111,7 @@ pub(crate) fn try_inline(
clean::ConstantItem(build_const(cx, did))
}
Res::Def(DefKind::Macro(kind), did) => {
- let mac = build_macro(cx, did, name, import_def_id);
+ let mac = build_macro(cx, did, name, import_def_id, kind);
let type_kind = match kind {
MacroKind::Bang => ItemType::Macro,
@@ -149,7 +124,7 @@ pub(crate) fn try_inline(
_ => return None,
};
- let (attrs, cfg) = merge_attrs(cx, Some(parent_module), load_attrs(cx, did), attrs);
+ let (attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs);
cx.inlined.insert(did.into());
let mut item =
clean::Item::from_def_id_and_attrs_and_parts(did, Some(name), kind, Box::new(attrs), cfg);
@@ -177,8 +152,7 @@ pub(crate) fn try_inline_glob(
// reexported by the glob, e.g. because they are shadowed by something else.
let reexports = cx
.tcx
- .module_reexports(current_mod)
- .unwrap_or_default()
+ .module_children_reexports(current_mod)
.iter()
.filter_map(|child| child.res.opt_def_id())
.collect();
@@ -316,9 +290,8 @@ fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box<clean::Typedef>
/// Builds all inherent implementations of an ADT (struct/union/enum) or Trait item/path/reexport.
pub(crate) fn build_impls(
cx: &mut DocContext<'_>,
- parent_module: Option<DefId>,
did: DefId,
- attrs: Option<&[ast::Attribute]>,
+ attrs: Option<(&[ast::Attribute], Option<DefId>)>,
ret: &mut Vec<clean::Item>,
) {
let _prof_timer = cx.tcx.sess.prof.generic_activity("build_inherent_impls");
@@ -326,7 +299,7 @@ pub(crate) fn build_impls(
// for each implementation of an item represented by `did`, build the clean::Item for that impl
for &did in tcx.inherent_impls(did).iter() {
- build_impl(cx, parent_module, did, attrs, ret);
+ build_impl(cx, did, attrs, ret);
}
// This pretty much exists expressly for `dyn Error` traits that exist in the `alloc` crate.
@@ -340,28 +313,26 @@ pub(crate) fn build_impls(
let type_ =
if tcx.is_trait(did) { TraitSimplifiedType(did) } else { AdtSimplifiedType(did) };
for &did in tcx.incoherent_impls(type_) {
- build_impl(cx, parent_module, did, attrs, ret);
+ build_impl(cx, did, attrs, ret);
}
}
}
-/// `parent_module` refers to the parent of the re-export, not the original item
pub(crate) fn merge_attrs(
cx: &mut DocContext<'_>,
- parent_module: Option<DefId>,
old_attrs: &[ast::Attribute],
- new_attrs: Option<&[ast::Attribute]>,
+ new_attrs: Option<(&[ast::Attribute], Option<DefId>)>,
) -> (clean::Attributes, Option<Arc<clean::cfg::Cfg>>) {
// NOTE: If we have additional attributes (from a re-export),
// always insert them first. This ensure that re-export
// doc comments show up before the original doc comments
// when we render them.
- if let Some(inner) = new_attrs {
+ if let Some((inner, item_id)) = new_attrs {
let mut both = inner.to_vec();
both.extend_from_slice(old_attrs);
(
- if let Some(new_id) = parent_module {
- Attributes::from_ast_with_additional(old_attrs, (inner, new_id))
+ if let Some(item_id) = item_id {
+ Attributes::from_ast_with_additional(old_attrs, (inner, item_id))
} else {
Attributes::from_ast(&both)
},
@@ -375,9 +346,8 @@ pub(crate) fn merge_attrs(
/// Inline an `impl`, inherent or of a trait. The `did` must be for an `impl`.
pub(crate) fn build_impl(
cx: &mut DocContext<'_>,
- parent_module: Option<DefId>,
did: DefId,
- attrs: Option<&[ast::Attribute]>,
+ attrs: Option<(&[ast::Attribute], Option<DefId>)>,
ret: &mut Vec<clean::Item>,
) {
if !cx.inlined.insert(did.into()) {
@@ -539,7 +509,7 @@ pub(crate) fn build_impl(
record_extern_trait(cx, did);
}
- let (merged_attrs, cfg) = merge_attrs(cx, parent_module, load_attrs(cx, did), attrs);
+ let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs);
trace!("merged_attrs={:?}", merged_attrs);
trace!(
@@ -587,7 +557,7 @@ fn build_module_items(
// If we're re-exporting a re-export it may actually re-export something in
// two namespaces, so the target may be listed twice. Make sure we only
// visit each node at most once.
- for &item in cx.tcx.module_children(did).iter() {
+ for item in cx.tcx.module_children(did).iter() {
if item.vis.is_public() {
let res = item.res.expect_non_local();
if let Some(def_id) = res.opt_def_id()
@@ -635,7 +605,7 @@ fn build_module_items(
cfg: None,
inline_stmt_id: None,
});
- } else if let Some(i) = try_inline(cx, did, None, res, item.ident.name, None, visited) {
+ } else if let Some(i) = try_inline(cx, res, item.ident.name, None, visited) {
items.extend(i)
}
}
@@ -681,18 +651,24 @@ fn build_macro(
def_id: DefId,
name: Symbol,
import_def_id: Option<DefId>,
+ macro_kind: MacroKind,
) -> clean::ItemKind {
match CStore::from_tcx(cx.tcx).load_macro_untracked(def_id, cx.sess()) {
- LoadedMacro::MacroDef(item_def, _) => {
- if let ast::ItemKind::MacroDef(ref def) = item_def.kind {
- let vis = cx.tcx.visibility(import_def_id.unwrap_or(def_id));
- clean::MacroItem(clean::Macro {
- source: utils::display_macro_source(cx, name, def, def_id, vis),
- })
- } else {
- unreachable!()
+ LoadedMacro::MacroDef(item_def, _) => match macro_kind {
+ MacroKind::Bang => {
+ if let ast::ItemKind::MacroDef(ref def) = item_def.kind {
+ let vis = cx.tcx.visibility(import_def_id.unwrap_or(def_id));
+ clean::MacroItem(clean::Macro {
+ source: utils::display_macro_source(cx, name, def, def_id, vis),
+ })
+ } else {
+ unreachable!()
+ }
}
- }
+ MacroKind::Derive | MacroKind::Attr => {
+ clean::ProcMacroItem(clean::ProcMacro { kind: macro_kind, helpers: Vec::new() })
+ }
+ },
LoadedMacro::ProcMacro(ext) => clean::ProcMacroItem(clean::ProcMacro {
kind: ext.macro_kind(),
helpers: ext.helper_attrs,
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 3edc2cd2e..5fa0c120f 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -21,25 +21,24 @@ use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId, LOCAL_CRATE};
use rustc_hir::PredicateOrigin;
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_infer::infer::region_constraints::{Constraint, RegionConstraintData};
+use rustc_middle::metadata::Reexport;
use rustc_middle::middle::resolve_bound_vars as rbv;
use rustc_middle::ty::fold::TypeFolder;
use rustc_middle::ty::InternalSubsts;
use rustc_middle::ty::TypeVisitableExt;
-use rustc_middle::ty::{self, AdtKind, DefIdTree, EarlyBinder, Ty, TyCtxt};
+use rustc_middle::ty::{self, AdtKind, EarlyBinder, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_span::hygiene::{AstPass, MacroKind};
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::{self, ExpnKind};
-use std::assert_matches::assert_matches;
+use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::collections::BTreeMap;
-use std::default::Default;
use std::hash::Hash;
use std::mem;
use thin_vec::ThinVec;
-use crate::clean::inline::merge_attrs;
use crate::core::{self, DocContext, ImplTraitParam};
use crate::formats::item_type::ItemType;
use crate::visit_ast::Module as DocModule;
@@ -270,15 +269,7 @@ fn clean_where_predicate<'tcx>(
let bound_params = wbp
.bound_generic_params
.iter()
- .map(|param| {
- // Higher-ranked params must be lifetimes.
- // Higher-ranked lifetimes can't have bounds.
- assert_matches!(
- param,
- hir::GenericParam { kind: hir::GenericParamKind::Lifetime { .. }, .. }
- );
- Lifetime(param.name.ident().name)
- })
+ .map(|param| clean_generic_param(cx, None, param))
.collect();
WherePredicate::BoundPredicate {
ty: clean_ty(wbp.bounded_ty, cx),
@@ -324,7 +315,7 @@ pub(crate) fn clean_predicate<'tcx>(
ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) => None,
ty::PredicateKind::Subtype(..)
- | ty::PredicateKind::AliasEq(..)
+ | ty::PredicateKind::AliasRelate(..)
| ty::PredicateKind::Coerce(..)
| ty::PredicateKind::ObjectSafe(..)
| ty::PredicateKind::ClosureKind(..)
@@ -410,7 +401,7 @@ fn clean_projection_predicate<'tcx>(
.collect_referenced_late_bound_regions(&pred)
.into_iter()
.filter_map(|br| match br {
- ty::BrNamed(_, name) if br.is_named() => Some(Lifetime(name)),
+ ty::BrNamed(_, name) if br.is_named() => Some(GenericParamDef::lifetime(name)),
_ => None,
})
.collect();
@@ -427,7 +418,7 @@ fn clean_projection<'tcx>(
cx: &mut DocContext<'tcx>,
def_id: Option<DefId>,
) -> Type {
- if cx.tcx.def_kind(ty.skip_binder().def_id) == DefKind::ImplTraitPlaceholder {
+ if cx.tcx.is_impl_trait_in_trait(ty.skip_binder().def_id) {
let bounds = cx
.tcx
.explicit_item_bounds(ty.skip_binder().def_id)
@@ -508,7 +499,6 @@ fn clean_generic_param_def<'tcx>(
ty::GenericParamDefKind::Const { has_default } => (
def.name,
GenericParamDefKind::Const {
- did: def.def_id,
ty: Box::new(clean_middle_ty(
ty::Binder::dummy(
cx.tcx
@@ -578,7 +568,6 @@ fn clean_generic_param<'tcx>(
hir::GenericParamKind::Const { ty, default } => (
param.name.ident().name,
GenericParamDefKind::Const {
- did: param.def_id.to_def_id(),
ty: Box::new(clean_ty(ty, cx)),
default: default
.map(|ct| Box::new(ty::Const::from_anon_const(cx.tcx, ct.def_id).to_string())),
@@ -831,7 +820,7 @@ fn clean_ty_generics<'tcx>(
p.get_bound_params()
.into_iter()
.flatten()
- .map(|param| GenericParamDef::lifetime(param.0))
+ .cloned()
.collect(),
));
}
@@ -919,6 +908,38 @@ fn clean_ty_generics<'tcx>(
}
}
+fn clean_proc_macro<'tcx>(
+ item: &hir::Item<'tcx>,
+ name: &mut Symbol,
+ kind: MacroKind,
+ cx: &mut DocContext<'tcx>,
+) -> ItemKind {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if kind == MacroKind::Derive &&
+ let Some(derive_name) = attrs
+ .lists(sym::proc_macro_derive)
+ .find_map(|mi| mi.ident())
+ {
+ *name = derive_name.name;
+ }
+
+ let mut helpers = Vec::new();
+ for mi in attrs.lists(sym::proc_macro_derive) {
+ if !mi.has_name(sym::attributes) {
+ continue;
+ }
+
+ if let Some(list) = mi.meta_item_list() {
+ for inner_mi in list {
+ if let Some(ident) = inner_mi.ident() {
+ helpers.push(ident.name);
+ }
+ }
+ }
+ }
+ ProcMacroItem(ProcMacro { kind, helpers })
+}
+
fn clean_fn_or_proc_macro<'tcx>(
item: &hir::Item<'tcx>,
sig: &hir::FnSig<'tcx>,
@@ -940,31 +961,7 @@ fn clean_fn_or_proc_macro<'tcx>(
}
});
match macro_kind {
- Some(kind) => {
- if kind == MacroKind::Derive {
- *name = attrs
- .lists(sym::proc_macro_derive)
- .find_map(|mi| mi.ident())
- .expect("proc-macro derives require a name")
- .name;
- }
-
- let mut helpers = Vec::new();
- for mi in attrs.lists(sym::proc_macro_derive) {
- if !mi.has_name(sym::attributes) {
- continue;
- }
-
- if let Some(list) = mi.meta_item_list() {
- for inner_mi in list {
- if let Some(ident) = inner_mi.ident() {
- helpers.push(ident.name);
- }
- }
- }
- }
- ProcMacroItem(ProcMacro { kind, helpers })
- }
+ Some(kind) => clean_proc_macro(item, name, kind, cx),
None => {
let mut func = clean_function(cx, sig, generics, FunctionArgs::Body(body_id));
clean_fn_decl_legacy_const_generics(&mut func, attrs);
@@ -2013,7 +2010,8 @@ fn clean_generic_args<'tcx>(
generic_args: &hir::GenericArgs<'tcx>,
cx: &mut DocContext<'tcx>,
) -> GenericArgs {
- if generic_args.parenthesized {
+ // FIXME(return_type_notation): Fix RTN parens rendering
+ if generic_args.parenthesized == hir::GenericArgsParentheses::ParenSugar {
let output = clean_ty(generic_args.bindings[0].ty(), cx);
let output = if output != Type::Tuple(Vec::new()) { Some(Box::new(output)) } else { None };
let inputs =
@@ -2066,110 +2064,44 @@ fn clean_bare_fn_ty<'tcx>(
BareFunctionDecl { unsafety: bare_fn.unsafety, abi: bare_fn.abi, decl, generic_params }
}
-/// This visitor is used to go through only the "top level" of a item and not enter any sub
-/// item while looking for a given `Ident` which is stored into `item` if found.
-struct OneLevelVisitor<'hir> {
- map: rustc_middle::hir::map::Map<'hir>,
- item: Option<&'hir hir::Item<'hir>>,
- looking_for: Ident,
+pub(crate) fn reexport_chain<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ import_def_id: LocalDefId,
target_def_id: LocalDefId,
-}
-
-impl<'hir> OneLevelVisitor<'hir> {
- fn new(map: rustc_middle::hir::map::Map<'hir>, target_def_id: LocalDefId) -> Self {
- Self { map, item: None, looking_for: Ident::empty(), target_def_id }
- }
-
- fn reset(&mut self, looking_for: Ident) {
- self.looking_for = looking_for;
- self.item = None;
- }
-}
-
-impl<'hir> hir::intravisit::Visitor<'hir> for OneLevelVisitor<'hir> {
- type NestedFilter = rustc_middle::hir::nested_filter::All;
-
- fn nested_visit_map(&mut self) -> Self::Map {
- self.map
- }
-
- fn visit_item(&mut self, item: &'hir hir::Item<'hir>) {
- if self.item.is_none()
- && item.ident == self.looking_for
- && (matches!(item.kind, hir::ItemKind::Use(_, _))
- || item.owner_id.def_id == self.target_def_id)
+) -> &'tcx [Reexport] {
+ for child in tcx.module_children_reexports(tcx.local_parent(import_def_id)) {
+ if child.res.opt_def_id() == Some(target_def_id.to_def_id())
+ && child.reexport_chain[0].id() == Some(import_def_id.to_def_id())
{
- self.item = Some(item);
+ return &child.reexport_chain;
}
}
+ &[]
}
-/// Because a `Use` item directly links to the imported item, we need to manually go through each
-/// import one by one. To do so, we go to the parent item and look for the `Ident` into it. Then,
-/// if we found the "end item" (the imported one), we stop there because we don't need its
-/// documentation. Otherwise, we repeat the same operation until we find the "end item".
+/// Collect attributes from the whole import chain.
fn get_all_import_attributes<'hir>(
- mut item: &hir::Item<'hir>,
- tcx: TyCtxt<'hir>,
+ cx: &mut DocContext<'hir>,
+ import_def_id: LocalDefId,
target_def_id: LocalDefId,
- attributes: &mut Vec<ast::Attribute>,
is_inline: bool,
-) {
+) -> Vec<(Cow<'hir, ast::Attribute>, Option<DefId>)> {
+ let mut attrs = Vec::new();
let mut first = true;
- let hir_map = tcx.hir();
- let mut visitor = OneLevelVisitor::new(hir_map, target_def_id);
- let mut visited = FxHashSet::default();
-
- // If the item is an import and has at least a path with two parts, we go into it.
- while let hir::ItemKind::Use(path, _) = item.kind && visited.insert(item.hir_id()) {
+ for def_id in reexport_chain(cx.tcx, import_def_id, target_def_id)
+ .iter()
+ .flat_map(|reexport| reexport.id())
+ {
+ let import_attrs = inline::load_attrs(cx, def_id);
if first {
// This is the "original" reexport so we get all its attributes without filtering them.
- attributes.extend_from_slice(hir_map.attrs(item.hir_id()));
+ attrs = import_attrs.iter().map(|attr| (Cow::Borrowed(attr), Some(def_id))).collect();
first = false;
} else {
- add_without_unwanted_attributes(attributes, hir_map.attrs(item.hir_id()), is_inline);
- }
-
- let def_id = if let [.., parent_segment, _] = &path.segments {
- match parent_segment.res {
- hir::def::Res::Def(_, def_id) => def_id,
- _ if parent_segment.ident.name == kw::Crate => {
- // In case the "parent" is the crate, it'll give `Res::Err` so we need to
- // circumvent it this way.
- tcx.parent(item.owner_id.def_id.to_def_id())
- }
- _ => break,
- }
- } else {
- // If the path doesn't have a parent, then the parent is the current module.
- tcx.parent(item.owner_id.def_id.to_def_id())
- };
-
- let Some(parent) = hir_map.get_if_local(def_id) else { break };
-
- // We get the `Ident` we will be looking for into `item`.
- let looking_for = path.segments[path.segments.len() - 1].ident;
- visitor.reset(looking_for);
-
- match parent {
- hir::Node::Item(parent_item) => {
- hir::intravisit::walk_item(&mut visitor, parent_item);
- }
- hir::Node::Crate(m) => {
- hir::intravisit::walk_mod(
- &mut visitor,
- m,
- tcx.local_def_id_to_hir_id(def_id.as_local().unwrap()),
- );
- }
- _ => break,
- }
- if let Some(i) = visitor.item {
- item = i;
- } else {
- break;
+ add_without_unwanted_attributes(&mut attrs, import_attrs, is_inline, Some(def_id));
}
}
+ attrs
}
fn filter_tokens_from_list(
@@ -2215,17 +2147,24 @@ fn filter_tokens_from_list(
/// * `doc(inline)`
/// * `doc(no_inline)`
/// * `doc(hidden)`
-fn add_without_unwanted_attributes(
- attrs: &mut Vec<ast::Attribute>,
- new_attrs: &[ast::Attribute],
+fn add_without_unwanted_attributes<'hir>(
+ attrs: &mut Vec<(Cow<'hir, ast::Attribute>, Option<DefId>)>,
+ new_attrs: &'hir [ast::Attribute],
is_inline: bool,
+ import_parent: Option<DefId>,
) {
- // If it's `#[doc(inline)]`, we don't want all attributes, otherwise we keep everything.
+ // If it's not `#[doc(inline)]`, we don't want all attributes, otherwise we keep everything.
if !is_inline {
- attrs.extend_from_slice(new_attrs);
+ for attr in new_attrs {
+ attrs.push((Cow::Borrowed(attr), import_parent));
+ }
return;
}
for attr in new_attrs {
+ if matches!(attr.kind, ast::AttrKind::DocComment(..)) {
+ attrs.push((Cow::Borrowed(attr), import_parent));
+ continue;
+ }
let mut attr = attr.clone();
match attr.kind {
ast::AttrKind::Normal(ref mut normal) => {
@@ -2252,18 +2191,15 @@ fn add_without_unwanted_attributes(
)
});
args.tokens = TokenStream::new(tokens);
- attrs.push(attr);
+ attrs.push((Cow::Owned(attr), import_parent));
}
ast::AttrArgs::Empty | ast::AttrArgs::Eq(..) => {
- attrs.push(attr);
- continue;
+ attrs.push((Cow::Owned(attr), import_parent));
}
}
}
}
- ast::AttrKind::DocComment(..) => {
- attrs.push(attr);
- }
+ _ => unreachable!(),
}
}
}
@@ -2318,16 +2254,17 @@ fn clean_maybe_renamed_item<'tcx>(
fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(),
}),
ItemKind::Impl(impl_) => return clean_impl(impl_, item.owner_id.def_id, cx),
- // proc macros can have a name set by attributes
- ItemKind::Fn(ref sig, generics, body_id) => {
- clean_fn_or_proc_macro(item, sig, generics, body_id, &mut name, cx)
- }
- ItemKind::Macro(ref macro_def, _) => {
+ ItemKind::Macro(ref macro_def, MacroKind::Bang) => {
let ty_vis = cx.tcx.visibility(def_id);
MacroItem(Macro {
source: display_macro_source(cx, name, macro_def, def_id, ty_vis),
})
}
+ ItemKind::Macro(_, macro_kind) => clean_proc_macro(item, &mut name, macro_kind, cx),
+ // proc macros can have a name set by attributes
+ ItemKind::Fn(ref sig, generics, body_id) => {
+ clean_fn_or_proc_macro(item, sig, generics, body_id, &mut name, cx)
+ }
ItemKind::Trait(_, _, generics, bounds, item_ids) => {
let items = item_ids
.iter()
@@ -2350,26 +2287,28 @@ fn clean_maybe_renamed_item<'tcx>(
_ => unreachable!("not yet converted"),
};
- let mut import_attrs = Vec::new();
- let mut target_attrs = Vec::new();
- if let Some(import_id) = import_id &&
- let Some(hir::Node::Item(use_node)) = cx.tcx.hir().find_by_def_id(import_id)
- {
- let is_inline = inline::load_attrs(cx, import_id.to_def_id()).lists(sym::doc).get_word_attr(sym::inline).is_some();
- // Then we get all the various imports' attributes.
- get_all_import_attributes(use_node, cx.tcx, item.owner_id.def_id, &mut import_attrs, is_inline);
- add_without_unwanted_attributes(&mut target_attrs, inline::load_attrs(cx, def_id), is_inline);
+ let target_attrs = inline::load_attrs(cx, def_id);
+ let attrs = if let Some(import_id) = import_id {
+ let is_inline = inline::load_attrs(cx, import_id.to_def_id())
+ .lists(sym::doc)
+ .get_word_attr(sym::inline)
+ .is_some();
+ let mut attrs =
+ get_all_import_attributes(cx, import_id, item.owner_id.def_id, is_inline);
+ add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None);
+ attrs
} else {
// We only keep the item's attributes.
- target_attrs.extend_from_slice(inline::load_attrs(cx, def_id));
- }
+ target_attrs.iter().map(|attr| (Cow::Borrowed(attr), None)).collect()
+ };
- let import_parent = import_id.map(|import_id| cx.tcx.local_parent(import_id).to_def_id());
- let (attrs, cfg) = merge_attrs(cx, import_parent, &target_attrs, Some(&import_attrs));
+ let cfg = attrs.cfg(cx.tcx, &cx.cache.hidden_cfg);
+ let attrs =
+ Attributes::from_ast_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false);
let mut item =
Item::from_def_id_and_attrs_and_parts(def_id, Some(name), kind, Box::new(attrs), cfg);
- item.inline_stmt_id = import_id.map(|def_id| def_id.to_def_id());
+ item.inline_stmt_id = import_id.map(|local| local.to_def_id());
vec![item]
})
}
@@ -2450,22 +2389,17 @@ fn clean_extern_crate<'tcx>(
Some(l) => attr::list_contains_name(&l, sym::inline),
None => false,
}
- });
+ })
+ && !cx.output_format.is_json();
let krate_owner_def_id = krate.owner_id.to_def_id();
if please_inline {
- let mut visited = DefIdSet::default();
-
- let res = Res::Def(DefKind::Mod, crate_def_id);
-
if let Some(items) = inline::try_inline(
cx,
- cx.tcx.parent_module(krate.hir_id()).to_def_id(),
- Some(krate_owner_def_id),
- res,
+ Res::Def(DefKind::Mod, crate_def_id),
name,
- Some(attrs),
- &mut visited,
+ Some((attrs, Some(krate_owner_def_id))),
+ &mut Default::default(),
) {
return items;
}
@@ -2589,17 +2523,13 @@ fn clean_use_statement_inner<'tcx>(
denied = true;
}
if !denied {
- let mut visited = DefIdSet::default();
let import_def_id = import.owner_id.to_def_id();
-
if let Some(mut items) = inline::try_inline(
cx,
- cx.tcx.parent_module(import.hir_id()).to_def_id(),
- Some(import_def_id),
path.res,
name,
- Some(attrs),
- &mut visited,
+ Some((attrs, Some(import_def_id))),
+ &mut Default::default(),
) {
items.push(Item::from_def_id_and_parts(
import_def_id,
diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs
index dbbc25739..3c72b0bf9 100644
--- a/src/librustdoc/clean/simplify.rs
+++ b/src/librustdoc/clean/simplify.rs
@@ -49,11 +49,7 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> ThinVec<WP
equalities.retain(|(lhs, rhs, bound_params)| {
let Some((ty, trait_did, name)) = lhs.projection() else { return true; };
let Some((bounds, _)) = tybounds.get_mut(ty) else { return true };
- let bound_params = bound_params
- .into_iter()
- .map(|param| clean::GenericParamDef::lifetime(param.0))
- .collect();
- merge_bounds(cx, bounds, bound_params, trait_did, name, rhs)
+ merge_bounds(cx, bounds, bound_params.clone(), trait_did, name, rhs)
});
// And finally, let's reassemble everything
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 27d18aad7..6d2ce9e28 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -1,5 +1,5 @@
+use std::borrow::Cow;
use std::cell::RefCell;
-use std::default::Default;
use std::hash::Hash;
use std::path::PathBuf;
use std::rc::Rc;
@@ -22,7 +22,7 @@ use rustc_hir::{BodyId, Mutability};
use rustc_hir_analysis::check::intrinsic::intrinsic_operation_unsafety;
use rustc_index::vec::IndexVec;
use rustc_middle::ty::fast_reject::SimplifiedType;
-use rustc_middle::ty::{self, DefIdTree, TyCtxt, Visibility};
+use rustc_middle::ty::{self, TyCtxt, Visibility};
use rustc_resolve::rustdoc::{add_doc_fragment, attrs_to_doc_fragments, inner_docs, DocFragment};
use rustc_session::Session;
use rustc_span::hygiene::MacroKind;
@@ -231,14 +231,6 @@ impl ExternalCrate {
hir::ItemKind::Mod(_) => {
as_keyword(Res::Def(DefKind::Mod, id.owner_id.to_def_id()))
}
- hir::ItemKind::Use(path, hir::UseKind::Single)
- if tcx.visibility(id.owner_id).is_public() =>
- {
- path.res
- .iter()
- .find_map(|res| as_keyword(res.expect_non_local()))
- .map(|(_, prim)| (id.owner_id.to_def_id(), prim))
- }
_ => None,
}
})
@@ -256,38 +248,24 @@ impl ExternalCrate {
//
// Note that this loop only searches the top-level items of the crate,
// and this is intentional. If we were to search the entire crate for an
- // item tagged with `#[doc(primitive)]` then we would also have to
+ // item tagged with `#[rustc_doc_primitive]` then we would also have to
// search the entirety of external modules for items tagged
- // `#[doc(primitive)]`, which is a pretty inefficient process (decoding
+ // `#[rustc_doc_primitive]`, which is a pretty inefficient process (decoding
// all that metadata unconditionally).
//
// In order to keep the metadata load under control, the
- // `#[doc(primitive)]` feature is explicitly designed to only allow the
+ // `#[rustc_doc_primitive]` feature is explicitly designed to only allow the
// primitive tags to show up as the top level items in a crate.
//
// Also note that this does not attempt to deal with modules tagged
// duplicately for the same primitive. This is handled later on when
// rendering by delegating everything to a hash map.
let as_primitive = |res: Res<!>| {
- if let Res::Def(DefKind::Mod, def_id) = res {
- let mut prim = None;
- let meta_items = tcx
- .get_attrs(def_id, sym::doc)
- .flat_map(|attr| attr.meta_item_list().unwrap_or_default());
- for meta in meta_items {
- if let Some(v) = meta.value_str() {
- if meta.has_name(sym::primitive) {
- prim = PrimitiveType::from_symbol(v);
- if prim.is_some() {
- break;
- }
- // FIXME: should warn on unknown primitives?
- }
- }
- }
- return prim.map(|p| (def_id, p));
- }
- None
+ let Res::Def(DefKind::Mod, def_id) = res else { return None };
+ tcx.get_attrs(def_id, sym::rustc_doc_primitive).find_map(|attr| {
+ // FIXME: should warn on unknown primitives?
+ Some((def_id, PrimitiveType::from_symbol(attr.value_str()?)?))
+ })
};
if root.is_local() {
@@ -301,15 +279,6 @@ impl ExternalCrate {
hir::ItemKind::Mod(_) => {
as_primitive(Res::Def(DefKind::Mod, id.owner_id.to_def_id()))
}
- hir::ItemKind::Use(path, hir::UseKind::Single)
- if tcx.visibility(id.owner_id).is_public() =>
- {
- path.res
- .iter()
- .find_map(|res| as_primitive(res.expect_non_local()))
- // Pretend the primitive is local.
- .map(|(_, prim)| (id.owner_id.to_def_id(), prim))
- }
_ => None,
}
})
@@ -482,10 +451,12 @@ impl Item {
pub(crate) fn links(&self, cx: &Context<'_>) -> Vec<RenderedLink> {
use crate::html::format::{href, link_tooltip};
- cx.cache()
+ let Some(links) = cx.cache()
.intra_doc_links
- .get(&self.item_id)
- .map_or(&[][..], |v| v.as_slice())
+ .get(&self.item_id) else {
+ return vec![]
+ };
+ links
.iter()
.filter_map(|ItemLink { link: s, link_text, page_id: id, ref fragment }| {
debug!(?id);
@@ -513,10 +484,12 @@ impl Item {
/// the link text, but does need to know which `[]`-bracketed names
/// are actually links.
pub(crate) fn link_names(&self, cache: &Cache) -> Vec<RenderedLink> {
- cache
+ let Some(links) = cache
.intra_doc_links
- .get(&self.item_id)
- .map_or(&[][..], |v| v.as_slice())
+ .get(&self.item_id) else {
+ return vec![];
+ };
+ links
.iter()
.map(|ItemLink { link: s, link_text, .. }| RenderedLink {
original_text: s.clone(),
@@ -713,7 +686,7 @@ impl Item {
return None;
}
// Variants always inherit visibility
- VariantItem(..) => return None,
+ VariantItem(..) | ImplItem(..) => return None,
// Trait items inherit the trait's visibility
AssocConstItem(..) | TyAssocConstItem(..) | AssocTypeItem(..) | TyAssocTypeItem(..)
| TyMethodItem(..) | MethodItem(..) => {
@@ -869,28 +842,13 @@ pub(crate) trait AttributesExt {
type AttributeIterator<'a>: Iterator<Item = ast::NestedMetaItem>
where
Self: 'a;
+ type Attributes<'a>: Iterator<Item = &'a ast::Attribute>
+ where
+ Self: 'a;
fn lists<'a>(&'a self, name: Symbol) -> Self::AttributeIterator<'a>;
- fn span(&self) -> Option<rustc_span::Span>;
-
- fn cfg(&self, tcx: TyCtxt<'_>, hidden_cfg: &FxHashSet<Cfg>) -> Option<Arc<Cfg>>;
-}
-
-impl AttributesExt for [ast::Attribute] {
- type AttributeIterator<'a> = impl Iterator<Item = ast::NestedMetaItem> + 'a;
-
- fn lists<'a>(&'a self, name: Symbol) -> Self::AttributeIterator<'a> {
- self.iter()
- .filter(move |attr| attr.has_name(name))
- .filter_map(ast::Attribute::meta_item_list)
- .flatten()
- }
-
- /// Return the span of the first doc-comment, if it exists.
- fn span(&self) -> Option<rustc_span::Span> {
- self.iter().find(|attr| attr.doc_str().is_some()).map(|attr| attr.span)
- }
+ fn iter<'a>(&'a self) -> Self::Attributes<'a>;
fn cfg(&self, tcx: TyCtxt<'_>, hidden_cfg: &FxHashSet<Cfg>) -> Option<Arc<Cfg>> {
let sess = tcx.sess;
@@ -980,11 +938,48 @@ impl AttributesExt for [ast::Attribute] {
}
}
+impl AttributesExt for [ast::Attribute] {
+ type AttributeIterator<'a> = impl Iterator<Item = ast::NestedMetaItem> + 'a;
+ type Attributes<'a> = impl Iterator<Item = &'a ast::Attribute> + 'a;
+
+ fn lists<'a>(&'a self, name: Symbol) -> Self::AttributeIterator<'a> {
+ self.iter()
+ .filter(move |attr| attr.has_name(name))
+ .filter_map(ast::Attribute::meta_item_list)
+ .flatten()
+ }
+
+ fn iter<'a>(&'a self) -> Self::Attributes<'a> {
+ self.into_iter()
+ }
+}
+
+impl AttributesExt for [(Cow<'_, ast::Attribute>, Option<DefId>)] {
+ type AttributeIterator<'a> = impl Iterator<Item = ast::NestedMetaItem> + 'a
+ where Self: 'a;
+ type Attributes<'a> = impl Iterator<Item = &'a ast::Attribute> + 'a
+ where Self: 'a;
+
+ fn lists<'a>(&'a self, name: Symbol) -> Self::AttributeIterator<'a> {
+ AttributesExt::iter(self)
+ .filter(move |attr| attr.has_name(name))
+ .filter_map(ast::Attribute::meta_item_list)
+ .flatten()
+ }
+
+ fn iter<'a>(&'a self) -> Self::Attributes<'a> {
+ self.into_iter().map(move |(attr, _)| match attr {
+ Cow::Borrowed(attr) => *attr,
+ Cow::Owned(attr) => attr,
+ })
+ }
+}
+
pub(crate) trait NestedAttributesExt {
/// Returns `true` if the attribute list contains a specific `word`
fn has_word(self, word: Symbol) -> bool
where
- Self: std::marker::Sized,
+ Self: Sized,
{
<Self as NestedAttributesExt>::get_word_attr(self, word).is_some()
}
@@ -1014,7 +1009,7 @@ pub(crate) fn collapse_doc_fragments(doc_strings: &[DocFragment]) -> String {
/// A link that has not yet been rendered.
///
/// This link will be turned into a rendered link by [`Item::links`].
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct ItemLink {
/// The original link written in the markdown
pub(crate) link: Box<str>,
@@ -1213,9 +1208,9 @@ impl Lifetime {
#[derive(Clone, Debug)]
pub(crate) enum WherePredicate {
- BoundPredicate { ty: Type, bounds: Vec<GenericBound>, bound_params: Vec<Lifetime> },
+ BoundPredicate { ty: Type, bounds: Vec<GenericBound>, bound_params: Vec<GenericParamDef> },
RegionPredicate { lifetime: Lifetime, bounds: Vec<GenericBound> },
- EqPredicate { lhs: Box<Type>, rhs: Box<Term>, bound_params: Vec<Lifetime> },
+ EqPredicate { lhs: Box<Type>, rhs: Box<Term>, bound_params: Vec<GenericParamDef> },
}
impl WherePredicate {
@@ -1227,7 +1222,7 @@ impl WherePredicate {
}
}
- pub(crate) fn get_bound_params(&self) -> Option<&[Lifetime]> {
+ pub(crate) fn get_bound_params(&self) -> Option<&[GenericParamDef]> {
match self {
Self::BoundPredicate { bound_params, .. } | Self::EqPredicate { bound_params, .. } => {
Some(bound_params)
@@ -1241,7 +1236,7 @@ impl WherePredicate {
pub(crate) enum GenericParamDefKind {
Lifetime { outlives: Vec<Lifetime> },
Type { did: DefId, bounds: Vec<GenericBound>, default: Option<Box<Type>>, synthetic: bool },
- Const { did: DefId, ty: Box<Type>, default: Option<Box<String>> },
+ Const { ty: Box<Type>, default: Option<Box<String>> },
}
impl GenericParamDefKind {
@@ -1471,27 +1466,68 @@ impl Type {
result
}
- /// Check if two types are "potentially the same".
+ pub(crate) fn is_borrowed_ref(&self) -> bool {
+ matches!(self, Type::BorrowedRef { .. })
+ }
+
+ /// Check if two types are "the same" for documentation purposes.
+ ///
/// This is different from `Eq`, because it knows that things like
/// `Placeholder` are possible matches for everything.
- pub(crate) fn is_same(&self, other: &Self, cache: &Cache) -> bool {
- match (self, other) {
+ ///
+ /// This relation is not commutative when generics are involved:
+ ///
+ /// ```ignore(private)
+ /// # // see types/tests.rs:is_same_generic for the real test
+ /// use rustdoc::format::cache::Cache;
+ /// use rustdoc::clean::types::{Type, PrimitiveType};
+ /// let cache = Cache::new(false);
+ /// let generic = Type::Generic(rustc_span::symbol::sym::Any);
+ /// let unit = Type::Primitive(PrimitiveType::Unit);
+ /// assert!(!generic.is_same(&unit, &cache));
+ /// assert!(unit.is_same(&generic, &cache));
+ /// ```
+ ///
+ /// An owned type is also the same as its borrowed variants (this is commutative),
+ /// but `&T` is not the same as `&mut T`.
+ pub(crate) fn is_doc_subtype_of(&self, other: &Self, cache: &Cache) -> bool {
+ // Strip the references so that it can compare the actual types, unless both are references.
+ // If both are references, leave them alone and compare the mutabilities later.
+ let (self_cleared, other_cleared) = if !self.is_borrowed_ref() || !other.is_borrowed_ref() {
+ (self.without_borrowed_ref(), other.without_borrowed_ref())
+ } else {
+ (self, other)
+ };
+ match (self_cleared, other_cleared) {
// Recursive cases.
(Type::Tuple(a), Type::Tuple(b)) => {
- a.len() == b.len() && a.iter().zip(b).all(|(a, b)| a.is_same(b, cache))
+ a.len() == b.len() && a.iter().zip(b).all(|(a, b)| a.is_doc_subtype_of(b, cache))
}
- (Type::Slice(a), Type::Slice(b)) => a.is_same(b, cache),
- (Type::Array(a, al), Type::Array(b, bl)) => al == bl && a.is_same(b, cache),
+ (Type::Slice(a), Type::Slice(b)) => a.is_doc_subtype_of(b, cache),
+ (Type::Array(a, al), Type::Array(b, bl)) => al == bl && a.is_doc_subtype_of(b, cache),
(Type::RawPointer(mutability, type_), Type::RawPointer(b_mutability, b_type_)) => {
- mutability == b_mutability && type_.is_same(b_type_, cache)
+ mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cache)
}
(
Type::BorrowedRef { mutability, type_, .. },
Type::BorrowedRef { mutability: b_mutability, type_: b_type_, .. },
- ) => mutability == b_mutability && type_.is_same(b_type_, cache),
- // Placeholders and generics are equal to all other types.
+ ) => mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cache),
+ // Placeholders are equal to all other types.
(Type::Infer, _) | (_, Type::Infer) => true,
- (Type::Generic(_), _) | (_, Type::Generic(_)) => true,
+ // Generics match everything on the right, but not on the left.
+ // If both sides are generic, this returns true.
+ (_, Type::Generic(_)) => true,
+ (Type::Generic(_), _) => false,
+ // Paths account for both the path itself and its generics.
+ (Type::Path { path: a }, Type::Path { path: b }) => {
+ a.def_id() == b.def_id()
+ && a.generics()
+ .zip(b.generics())
+ .map(|(ag, bg)| {
+ ag.iter().zip(bg.iter()).all(|(at, bt)| at.is_doc_subtype_of(bt, cache))
+ })
+ .unwrap_or(true)
+ }
// Other cases, such as primitives, just use recursion.
(a, b) => a
.def_id(cache)
@@ -1782,13 +1818,17 @@ impl PrimitiveType {
}
}
- /// Returns the DefId of the module with `doc(primitive)` for this primitive type.
+ /// Returns the DefId of the module with `rustc_doc_primitive` for this primitive type.
/// Panics if there is no such module.
///
- /// This gives precedence to primitives defined in the current crate, and deprioritizes primitives defined in `core`,
- /// but otherwise, if multiple crates define the same primitive, there is no guarantee of which will be picked.
- /// In particular, if a crate depends on both `std` and another crate that also defines `doc(primitive)`, then
- /// it's entirely random whether `std` or the other crate is picked. (no_std crates are usually fine unless multiple dependencies define a primitive.)
+ /// This gives precedence to primitives defined in the current crate, and deprioritizes
+ /// primitives defined in `core`,
+ /// but otherwise, if multiple crates define the same primitive, there is no guarantee of which
+ /// will be picked.
+ ///
+ /// In particular, if a crate depends on both `std` and another crate that also defines
+ /// `rustc_doc_primitive`, then it's entirely random whether `std` or the other crate is picked.
+ /// (no_std crates are usually fine unless multiple dependencies define a primitive.)
pub(crate) fn primitive_locations(tcx: TyCtxt<'_>) -> &FxHashMap<PrimitiveType, DefId> {
static PRIMITIVE_LOCATIONS: OnceCell<FxHashMap<PrimitiveType, DefId>> = OnceCell::new();
PRIMITIVE_LOCATIONS.get_or_init(|| {
@@ -1978,7 +2018,7 @@ impl Variant {
#[derive(Clone, Debug)]
pub(crate) struct Discriminant {
- // In the case of cross crate re-exports, we don't have the nessesary information
+ // In the case of cross crate re-exports, we don't have the necessary information
// to reconstruct the expression of the discriminant, only the value.
pub(super) expr: Option<BodyId>,
pub(super) value: DefId,
diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs
index 20627c2cf..d8c91a968 100644
--- a/src/librustdoc/clean/types/tests.rs
+++ b/src/librustdoc/clean/types/tests.rs
@@ -10,7 +10,7 @@ use rustc_span::symbol::Symbol;
fn create_doc_fragment(s: &str) -> Vec<DocFragment> {
vec![DocFragment {
span: DUMMY_SP,
- parent_module: None,
+ item_id: None,
doc: Symbol::intern(s),
kind: DocFragmentKind::SugaredDoc,
indent: 0,
@@ -69,3 +69,14 @@ fn should_not_trim() {
run_test("\t line1 \n\t line2", "line1 \nline2");
run_test(" \tline1 \n \tline2", "line1 \nline2");
}
+
+#[test]
+fn is_same_generic() {
+ use crate::clean::types::{PrimitiveType, Type};
+ use crate::formats::cache::Cache;
+ let cache = Cache::new(false);
+ let generic = Type::Generic(rustc_span::symbol::sym::Any);
+ let unit = Type::Primitive(PrimitiveType::Unit);
+ assert!(!generic.is_doc_subtype_of(&unit, &cache));
+ assert!(unit.is_doc_subtype_of(&generic, &cache));
+}
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index c9c1c2c45..cca50df0d 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -17,7 +17,7 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_middle::mir;
use rustc_middle::mir::interpret::ConstValue;
use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
-use rustc_middle::ty::{self, DefIdTree, TyCtxt};
+use rustc_middle::ty::{self, TyCtxt};
use rustc_span::symbol::{kw, sym, Symbol};
use std::fmt::Write as _;
use std::mem;
@@ -195,12 +195,12 @@ pub(crate) fn build_deref_target_impls(
if let Some(prim) = target.primitive_type() {
let _prof_timer = cx.tcx.sess.prof.generic_activity("build_primitive_inherent_impls");
for did in prim.impls(tcx).filter(|did| !did.is_local()) {
- inline::build_impl(cx, None, did, None, ret);
+ inline::build_impl(cx, did, None, ret);
}
} else if let Type::Path { path } = target {
let did = path.def_id();
if !did.is_local() {
- inline::build_impls(cx, None, did, None, ret);
+ inline::build_impls(cx, did, None, ret);
}
}
}
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index 2c514a0c8..1be4f364e 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -1,12 +1,10 @@
use std::collections::BTreeMap;
-use std::convert::TryFrom;
use std::ffi::OsStr;
use std::fmt;
use std::path::PathBuf;
use std::str::FromStr;
use rustc_data_structures::fx::FxHashMap;
-use rustc_driver::print_flag_list;
use rustc_session::config::{
self, parse_crate_types_from_list, parse_externs, parse_target_triple, CrateType,
};
@@ -31,18 +29,13 @@ use crate::passes::{self, Condition};
use crate::scrape_examples::{AllCallLocations, ScrapeExamplesOptions};
use crate::theme;
-#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub(crate) enum OutputFormat {
Json,
+ #[default]
Html,
}
-impl Default for OutputFormat {
- fn default() -> OutputFormat {
- OutputFormat::Html
- }
-}
-
impl OutputFormat {
pub(crate) fn is_json(&self) -> bool {
matches!(self, OutputFormat::Json)
@@ -235,7 +228,7 @@ pub(crate) struct RenderOptions {
pub(crate) extension_css: Option<PathBuf>,
/// A map of crate names to the URL to use instead of querying the crate's `html_root_url`.
pub(crate) extern_html_root_urls: BTreeMap<String, String>,
- /// Whether to give precedence to `html_root_url` or `--exten-html-root-url`.
+ /// Whether to give precedence to `html_root_url` or `--extern-html-root-url`.
pub(crate) extern_html_root_takes_precedence: bool,
/// A map of the default settings (values are as for DOM storage API). Keys should lack the
/// `rustdoc-` prefix.
@@ -333,14 +326,7 @@ impl Options {
return Err(0);
}
- let z_flags = matches.opt_strs("Z");
- if z_flags.iter().any(|x| *x == "help") {
- print_flag_list("-Z", config::Z_OPTIONS);
- return Err(0);
- }
- let c_flags = matches.opt_strs("C");
- if c_flags.iter().any(|x| *x == "help") {
- print_flag_list("-C", config::CG_OPTIONS);
+ if rustc_driver::describe_flag_categories(&matches) {
return Err(0);
}
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index fbfc58a43..b392ba058 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -217,13 +217,8 @@ pub(crate) fn create_config(
let crate_types =
if proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
- let resolve_doc_links = if *document_private {
- ResolveDocLinks::All
- } else {
- // Should be `ResolveDocLinks::Exported` in theory, but for some reason rustdoc
- // still tries to request resolutions for links on private items.
- ResolveDocLinks::All
- };
+ let resolve_doc_links =
+ if *document_private { ResolveDocLinks::All } else { ResolveDocLinks::Exported };
let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false);
// plays with error output here!
let sessopts = config::Options {
@@ -308,6 +303,9 @@ pub(crate) fn run_global_ctxt(
// HACK(jynelson) this calls an _extremely_ limited subset of `typeck`
// and might break if queries change their assumptions in the future.
+ tcx.sess.time("type_collecting", || {
+ tcx.hir().for_each_module(|module| tcx.ensure().collect_mod_item_types(module))
+ });
// NOTE: This is copy/pasted from typeck/lib.rs and should be kept in sync with those changes.
tcx.sess.time("item_types_checking", || {
diff --git a/src/librustdoc/docfs.rs b/src/librustdoc/docfs.rs
index be066bdaf..d58b8dc6a 100644
--- a/src/librustdoc/docfs.rs
+++ b/src/librustdoc/docfs.rs
@@ -2,18 +2,20 @@
//!
//! On Windows this indirects IO into threads to work around performance issues
//! with Defender (and other similar virus scanners that do blocking operations).
-//! On other platforms this is a thin shim to fs.
//!
//! Only calls needed to permit this workaround have been abstracted: thus
//! fs::read is still done directly via the fs module; if in future rustdoc
//! needs to read-after-write from a file, then it would be added to this
//! abstraction.
+use std::cmp::max;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::string::ToString;
use std::sync::mpsc::Sender;
+use std::thread::available_parallelism;
+use threadpool::ThreadPool;
pub(crate) trait PathError {
fn new<S, P: AsRef<Path>>(e: S, path: P) -> Self
@@ -24,11 +26,21 @@ pub(crate) trait PathError {
pub(crate) struct DocFS {
sync_only: bool,
errors: Option<Sender<String>>,
+ pool: ThreadPool,
}
impl DocFS {
pub(crate) fn new(errors: Sender<String>) -> DocFS {
- DocFS { sync_only: false, errors: Some(errors) }
+ const MINIMUM_NB_THREADS: usize = 2;
+ DocFS {
+ sync_only: false,
+ errors: Some(errors),
+ pool: ThreadPool::new(
+ available_parallelism()
+ .map(|nb| max(nb.get(), MINIMUM_NB_THREADS))
+ .unwrap_or(MINIMUM_NB_THREADS),
+ ),
+ }
}
pub(crate) fn set_sync_only(&mut self, sync_only: bool) {
@@ -54,12 +66,11 @@ impl DocFS {
where
E: PathError,
{
- #[cfg(windows)]
if !self.sync_only {
// A possible future enhancement after more detailed profiling would
// be to create the file sync so errors are reported eagerly.
let sender = self.errors.clone().expect("can't write after closing");
- rayon::spawn(move || {
+ self.pool.execute(move || {
fs::write(&path, contents).unwrap_or_else(|e| {
sender.send(format!("\"{}\": {}", path.display(), e)).unwrap_or_else(|_| {
panic!("failed to send error on \"{}\"", path.display())
@@ -70,9 +81,12 @@ impl DocFS {
fs::write(&path, contents).map_err(|e| E::new(e, path))?;
}
- #[cfg(not(windows))]
- fs::write(&path, contents).map_err(|e| E::new(e, path))?;
-
Ok(())
}
}
+
+impl Drop for DocFS {
+ fn drop(&mut self) {
+ self.pool.join();
+ }
+}
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index 9cf84acc7..daf10e5b8 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -398,6 +398,8 @@ fn run_test(
compiler.stdin(Stdio::piped());
compiler.stderr(Stdio::piped());
+ debug!("compiler invocation for doctest: {:?}", compiler);
+
let mut child = compiler.spawn().expect("Failed to spawn rustc process");
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
@@ -1057,6 +1059,16 @@ impl Tester for Collector {
Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
},
ignore_message: None,
+ #[cfg(not(bootstrap))]
+ source_file: "",
+ #[cfg(not(bootstrap))]
+ start_line: 0,
+ #[cfg(not(bootstrap))]
+ start_col: 0,
+ #[cfg(not(bootstrap))]
+ end_line: 0,
+ #[cfg(not(bootstrap))]
+ end_col: 0,
// compiler failures are test failures
should_panic: test::ShouldPanic::No,
compile_fail: config.compile_fail,
@@ -1229,8 +1241,9 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
if let Some(doc) = attrs.collapsed_doc_value() {
// Use the outermost invocation, so that doctest names come from where the docs were written.
let span = ast_attrs
- .span()
- .map(|span| span.ctxt().outer_expn().expansion_cause().unwrap_or(span))
+ .iter()
+ .find(|attr| attr.doc_str().is_some())
+ .map(|attr| attr.span.ctxt().outer_expn().expansion_cause().unwrap_or(attr.span))
.unwrap_or(DUMMY_SP);
self.collector.set_position(span);
markdown::find_testable_code(
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index 8dbfaf4bb..c03291820 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -1,6 +1,6 @@
use std::mem;
-use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet};
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::Symbol;
@@ -118,7 +118,7 @@ pub(crate) struct Cache {
/// All intra-doc links resolved so far.
///
/// Links are indexed by the DefId of the item they document.
- pub(crate) intra_doc_links: FxHashMap<ItemId, Vec<clean::ItemLink>>,
+ pub(crate) intra_doc_links: FxHashMap<ItemId, FxIndexSet<clean::ItemLink>>,
/// Cfg that have been hidden via #![doc(cfg_hide(...))]
pub(crate) hidden_cfg: FxHashSet<clean::cfg::Cfg>,
}
@@ -346,6 +346,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
self.cache,
),
aliases: item.attrs.get_doc_aliases(),
+ deprecation: item.deprecation(self.tcx),
});
}
}
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 0e4c5ed68..1b445b898 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -1,13 +1,15 @@
//! HTML formatting module
//!
//! This module contains a large number of `fmt::Display` implementations for
-//! various types in `rustdoc::clean`. These implementations all currently
-//! assume that HTML output is desired, although it may be possible to redesign
-//! them in the future to instead emit any format desired.
+//! various types in `rustdoc::clean`.
+//!
+//! These implementations all emit HTML. As an internal implementation detail,
+//! some of them support an alternate format that emits text, but that should
+//! not be used external to this module.
use std::borrow::Cow;
use std::cell::Cell;
-use std::fmt;
+use std::fmt::{self, Write};
use std::iter::{self, once};
use rustc_ast as ast;
@@ -19,7 +21,6 @@ use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_metadata::creader::{CStore, LoadedMacro};
use rustc_middle::ty;
-use rustc_middle::ty::DefIdTree;
use rustc_middle::ty::TyCtxt;
use rustc_span::symbol::kw;
use rustc_span::{sym, Symbol};
@@ -127,7 +128,6 @@ impl Buffer {
// the fmt::Result return type imposed by fmt::Write (and avoiding the trait
// import).
pub(crate) fn write_fmt(&mut self, v: fmt::Arguments<'_>) {
- use fmt::Write;
self.buffer.write_fmt(v).unwrap();
}
@@ -136,10 +136,6 @@ impl Buffer {
self.into_inner()
}
- pub(crate) fn is_for_html(&self) -> bool {
- self.for_html
- }
-
pub(crate) fn reserve(&mut self, additional: usize) {
self.buffer.reserve(additional)
}
@@ -280,8 +276,6 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
indent: usize,
ending: Ending,
) -> impl fmt::Display + 'a + Captures<'tcx> {
- use fmt::Write;
-
display_fn(move |f| {
let mut where_predicates = gens.where_predicates.iter().filter(|pred| {
!matches!(pred, clean::WherePredicate::BoundPredicate { bounds, .. } if bounds.is_empty())
@@ -309,13 +303,13 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
write!(
f,
"for<{:#}> {ty_cx:#}: {generic_bounds:#}",
- comma_sep(bound_params.iter().map(|lt| lt.print()), true)
+ comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true)
)
} else {
write!(
f,
"for&lt;{}&gt; {ty_cx}: {generic_bounds}",
- comma_sep(bound_params.iter().map(|lt| lt.print()), true)
+ comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true)
)
}
}
@@ -355,10 +349,10 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
let mut br_with_padding = String::with_capacity(6 * indent + 28);
br_with_padding.push_str("\n");
- let padding_amout =
+ let padding_amount =
if ending == Ending::Newline { indent + 4 } else { indent + "fn where ".len() };
- for _ in 0..padding_amout {
+ for _ in 0..padding_amount {
br_with_padding.push_str(" ");
}
let where_preds = where_preds.to_string().replace('\n', &br_with_padding);
@@ -773,6 +767,12 @@ pub(crate) fn link_tooltip(did: DefId, fragment: &Option<UrlFragment>, cx: &Cont
.or_else(|| cache.external_paths.get(&did))
else { return String::new() };
let mut buf = Buffer::new();
+ let fqp = if *shortty == ItemType::Primitive {
+ // primitives are documented in a crate, but not actually part of it
+ &fqp[fqp.len() - 1..]
+ } else {
+ &fqp
+ };
if let &Some(UrlFragment::Item(id)) = fragment {
write!(buf, "{} ", cx.tcx().def_descr(id));
for component in fqp {
@@ -1138,22 +1138,21 @@ fn fmt_type<'cx>(
// the ugliness comes from inlining across crates where
// everything comes in as a fully resolved QPath (hard to
// look at).
- match href(trait_.def_id(), cx) {
- Ok((ref url, _, ref path)) if !f.alternate() => {
- write!(
- f,
- "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
- title=\"type {path}::{name}\">{name}</a>{args}",
- url = url,
- shortty = ItemType::AssocType,
- name = assoc.name,
- path = join_with_double_colon(path),
- args = assoc.args.print(cx),
- )?;
- }
- _ => write!(f, "{}{:#}", assoc.name, assoc.args.print(cx))?,
- }
- Ok(())
+ if !f.alternate() && let Ok((url, _, path)) = href(trait_.def_id(), cx) {
+ write!(
+ f,
+ "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
+ title=\"type {path}::{name}\">{name}</a>",
+ shortty = ItemType::AssocType,
+ name = assoc.name,
+ path = join_with_double_colon(&path),
+ )
+ } else {
+ write!(f, "{}", assoc.name)
+ }?;
+
+ // Carry `f.alternate()` into this display w/o branching manually.
+ fmt::Display::fmt(&assoc.args.print(cx), f)
}
}
}
@@ -1307,6 +1306,28 @@ impl clean::BareFunctionDecl {
}
}
+// Implements Write but only counts the bytes "written".
+struct WriteCounter(usize);
+
+impl std::fmt::Write for WriteCounter {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ self.0 += s.len();
+ Ok(())
+ }
+}
+
+// Implements Display by emitting the given number of spaces.
+struct Indent(usize);
+
+impl fmt::Display for Indent {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ (0..self.0).for_each(|_| {
+ f.write_char(' ').unwrap();
+ });
+ Ok(())
+ }
+}
+
impl clean::FnDecl {
pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>(
&'a self,
@@ -1346,95 +1367,80 @@ impl clean::FnDecl {
indent: usize,
cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> {
- display_fn(move |f| self.inner_full_print(header_len, indent, f, cx))
+ display_fn(move |f| {
+ // First, generate the text form of the declaration, with no line wrapping, and count the bytes.
+ let mut counter = WriteCounter(0);
+ write!(&mut counter, "{:#}", display_fn(|f| { self.inner_full_print(None, f, cx) }))
+ .unwrap();
+ // If the text form was over 80 characters wide, we will line-wrap our output.
+ let line_wrapping_indent =
+ if header_len + counter.0 > 80 { Some(indent) } else { None };
+ // Generate the final output. This happens to accept `{:#}` formatting to get textual
+ // output but in practice it is only formatted with `{}` to get HTML output.
+ self.inner_full_print(line_wrapping_indent, f, cx)
+ })
}
fn inner_full_print(
&self,
- header_len: usize,
- indent: usize,
+ // For None, the declaration will not be line-wrapped. For Some(n),
+ // the declaration will be line-wrapped, with an indent of n spaces.
+ line_wrapping_indent: Option<usize>,
f: &mut fmt::Formatter<'_>,
cx: &Context<'_>,
) -> fmt::Result {
let amp = if f.alternate() { "&" } else { "&amp;" };
- let mut args = Buffer::html();
- let mut args_plain = Buffer::new();
+
+ write!(f, "(")?;
+ if let Some(n) = line_wrapping_indent {
+ write!(f, "\n{}", Indent(n + 4))?;
+ }
for (i, input) in self.inputs.values.iter().enumerate() {
+ if i > 0 {
+ match line_wrapping_indent {
+ None => write!(f, ", ")?,
+ Some(n) => write!(f, ",\n{}", Indent(n + 4))?,
+ };
+ }
if let Some(selfty) = input.to_self() {
match selfty {
clean::SelfValue => {
- args.push_str("self");
- args_plain.push_str("self");
+ write!(f, "self")?;
}
clean::SelfBorrowed(Some(ref lt), mtbl) => {
- write!(args, "{}{} {}self", amp, lt.print(), mtbl.print_with_space());
- write!(args_plain, "&{} {}self", lt.print(), mtbl.print_with_space());
+ write!(f, "{}{} {}self", amp, lt.print(), mtbl.print_with_space())?;
}
clean::SelfBorrowed(None, mtbl) => {
- write!(args, "{}{}self", amp, mtbl.print_with_space());
- write!(args_plain, "&{}self", mtbl.print_with_space());
+ write!(f, "{}{}self", amp, mtbl.print_with_space())?;
}
clean::SelfExplicit(ref typ) => {
- if f.alternate() {
- write!(args, "self: {:#}", typ.print(cx));
- } else {
- write!(args, "self: {}", typ.print(cx));
- }
- write!(args_plain, "self: {:#}", typ.print(cx));
+ write!(f, "self: ")?;
+ fmt::Display::fmt(&typ.print(cx), f)?;
}
}
} else {
- if i > 0 {
- args.push_str("\n");
- }
if input.is_const {
- args.push_str("const ");
- args_plain.push_str("const ");
- }
- write!(args, "{}: ", input.name);
- write!(args_plain, "{}: ", input.name);
-
- if f.alternate() {
- write!(args, "{:#}", input.type_.print(cx));
- } else {
- write!(args, "{}", input.type_.print(cx));
+ write!(f, "const ")?;
}
- write!(args_plain, "{:#}", input.type_.print(cx));
- }
- if i + 1 < self.inputs.values.len() {
- args.push_str(",");
- args_plain.push_str(",");
+ write!(f, "{}: ", input.name)?;
+ fmt::Display::fmt(&input.type_.print(cx), f)?;
}
}
- let mut args_plain = format!("({})", args_plain.into_inner());
- let mut args = args.into_inner();
-
if self.c_variadic {
- args.push_str(",\n ...");
- args_plain.push_str(", ...");
+ match line_wrapping_indent {
+ None => write!(f, ", ...")?,
+ Some(n) => write!(f, "\n{}...", Indent(n + 4))?,
+ };
}
- let arrow_plain = format!("{:#}", self.output.print(cx));
- let arrow =
- if f.alternate() { arrow_plain.clone() } else { format!("{}", self.output.print(cx)) };
-
- let declaration_len = header_len + args_plain.len() + arrow_plain.len();
- let output = if declaration_len > 80 {
- let full_pad = format!("\n{}", " ".repeat(indent + 4));
- let close_pad = format!("\n{}", " ".repeat(indent));
- format!(
- "({pad}{args}{close}){arrow}",
- pad = if self.inputs.values.is_empty() { "" } else { &full_pad },
- args = args.replace('\n', &full_pad),
- close = close_pad,
- arrow = arrow
- )
- } else {
- format!("({args}){arrow}", args = args.replace('\n', " "), arrow = arrow)
+ match line_wrapping_indent {
+ None => write!(f, ")")?,
+ Some(n) => write!(f, "\n{})", Indent(n))?,
};
- write!(f, "{}", output)
+ fmt::Display::fmt(&self.output.print(cx), f)?;
+ Ok(())
}
}
@@ -1469,7 +1475,7 @@ pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>(
debug!("path={:?}", path);
// modified from `resolved_path()` to work with `DefPathData`
let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
- let anchor = anchor(vis_did, last_name, cx).to_string();
+ let anchor = anchor(vis_did, last_name, cx);
let mut s = "pub(in ".to_owned();
for seg in &path.data[..path.data.len() - 1] {
@@ -1491,9 +1497,9 @@ pub(crate) fn visibility_to_src_with_space<'a, 'tcx: 'a>(
tcx: TyCtxt<'tcx>,
item_did: DefId,
) -> impl fmt::Display + 'a + Captures<'tcx> {
- let to_print = match visibility {
- None => String::new(),
- Some(ty::Visibility::Public) => "pub ".to_owned(),
+ let to_print: Cow<'static, str> = match visibility {
+ None => "".into(),
+ Some(ty::Visibility::Public) => "pub ".into(),
Some(ty::Visibility::Restricted(vis_did)) => {
// FIXME(camelid): This may not work correctly if `item_did` is a module.
// However, rustdoc currently never displays a module's
@@ -1501,17 +1507,17 @@ pub(crate) fn visibility_to_src_with_space<'a, 'tcx: 'a>(
let parent_module = find_nearest_parent_module(tcx, item_did);
if vis_did.is_crate_root() {
- "pub(crate) ".to_owned()
+ "pub(crate) ".into()
} else if parent_module == Some(vis_did) {
// `pub(in foo)` where `foo` is the parent module
// is the same as no visibility modifier
- String::new()
+ "".into()
} else if parent_module.and_then(|parent| find_nearest_parent_module(tcx, parent))
== Some(vis_did)
{
- "pub(super) ".to_owned()
+ "pub(super) ".into()
} else {
- format!("pub(in {}) ", tcx.def_path_str(vis_did))
+ format!("pub(in {}) ", tcx.def_path_str(vis_did)).into()
}
}
};
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index 2c9fc4e3c..b61dd5714 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -65,23 +65,6 @@ pub(crate) fn render_item_decl_with_highlighting(src: &str, out: &mut Buffer) {
write!(out, "</pre>");
}
-/// Highlights `src` as a source code page, returning the HTML output.
-pub(crate) fn render_source_with_highlighting(
- src: &str,
- out: &mut Buffer,
- line_numbers: Buffer,
- href_context: HrefContext<'_, '_>,
- decoration_info: DecorationInfo,
- extra: Option<&str>,
-) {
- write_header(out, "", Some(line_numbers), Tooltip::None);
- if let Some(extra) = extra {
- out.push_str(extra);
- }
- write_code(out, src, Some(href_context), Some(decoration_info));
- write_footer(out, None);
-}
-
fn write_header(out: &mut Buffer, class: &str, extra_content: Option<Buffer>, tooltip: Tooltip) {
write!(
out,
@@ -143,8 +126,8 @@ fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
/// This type is used as a conveniency to prevent having to pass all its fields as arguments into
/// the various functions (which became its methods).
-struct TokenHandler<'a, 'tcx> {
- out: &'a mut Buffer,
+struct TokenHandler<'a, 'tcx, F: Write> {
+ out: &'a mut F,
/// It contains the closing tag and the associated `Class`.
closing_tags: Vec<(&'static str, Class)>,
/// This is used because we don't automatically generate the closing tag on `ExitSpan` in
@@ -159,7 +142,7 @@ struct TokenHandler<'a, 'tcx> {
href_context: Option<HrefContext<'a, 'tcx>>,
}
-impl<'a, 'tcx> TokenHandler<'a, 'tcx> {
+impl<'a, 'tcx, F: Write> TokenHandler<'a, 'tcx, F> {
fn handle_exit_span(&mut self) {
// We can't get the last `closing_tags` element using `pop()` because `closing_tags` is
// being used in `write_pending_elems`.
@@ -194,8 +177,8 @@ impl<'a, 'tcx> TokenHandler<'a, 'tcx> {
} 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 && current_class.is_some() {
- Some(enter_span(self.out, current_class.unwrap(), &self.href_context))
+ 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
};
@@ -211,7 +194,7 @@ impl<'a, 'tcx> TokenHandler<'a, 'tcx> {
}
}
-impl<'a, 'tcx> Drop for TokenHandler<'a, 'tcx> {
+impl<'a, 'tcx, F: Write> Drop for TokenHandler<'a, 'tcx, F> {
/// When leaving, we need to flush all pending data to not have missing content.
fn drop(&mut self) {
if self.pending_exit_span.is_some() {
@@ -233,8 +216,8 @@ impl<'a, 'tcx> Drop for TokenHandler<'a, 'tcx> {
/// item definition.
///
/// More explanations about spans and how we use them here are provided in the
-fn write_code(
- out: &mut Buffer,
+pub(super) fn write_code(
+ out: &mut impl Write,
src: &str,
href_context: Option<HrefContext<'_, '_>>,
decoration_info: Option<DecorationInfo>,
@@ -883,7 +866,7 @@ impl<'src> Classifier<'src> {
/// Called when we start processing a span of text that should be highlighted.
/// The `Class` argument specifies how it should be highlighted.
fn enter_span(
- out: &mut Buffer,
+ out: &mut impl Write,
klass: Class,
href_context: &Option<HrefContext<'_, '_>>,
) -> &'static str {
@@ -894,8 +877,8 @@ fn enter_span(
}
/// Called at the end of a span of highlighted text.
-fn exit_span(out: &mut Buffer, closing_tag: &str) {
- out.write_str(closing_tag);
+fn exit_span(out: &mut impl Write, closing_tag: &str) {
+ out.write_str(closing_tag).unwrap();
}
/// Called for a span of text. If the text should be highlighted differently
@@ -915,7 +898,7 @@ fn exit_span(out: &mut Buffer, closing_tag: &str) {
/// will then try to find this `span` in the `span_correspondance_map`. If found, it'll then
/// generate a link for this element (which corresponds to where its definition is located).
fn string<T: Display>(
- out: &mut Buffer,
+ out: &mut impl Write,
text: T,
klass: Option<Class>,
href_context: &Option<HrefContext<'_, '_>>,
@@ -923,7 +906,7 @@ fn string<T: Display>(
) {
if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context, open_tag)
{
- out.write_str(closing_tag);
+ out.write_str(closing_tag).unwrap();
}
}
@@ -937,7 +920,7 @@ fn string<T: Display>(
/// in `span_map.rs::collect_spans_and_sources`. If it cannot retrieve the information, then it's
/// the same as the second point (`klass` is `Some` but doesn't have a [`rustc_span::Span`]).
fn string_without_closing_tag<T: Display>(
- out: &mut Buffer,
+ out: &mut impl Write,
text: T,
klass: Option<Class>,
href_context: &Option<HrefContext<'_, '_>>,
@@ -945,16 +928,16 @@ fn string_without_closing_tag<T: Display>(
) -> Option<&'static str> {
let Some(klass) = klass
else {
- write!(out, "{}", text);
+ write!(out, "{}", text).unwrap();
return None;
};
let Some(def_span) = klass.get_span()
else {
if !open_tag {
- write!(out, "{}", text);
+ write!(out, "{}", text).unwrap();
return None;
}
- write!(out, "<span class=\"{}\">{}", klass.as_html(), text);
+ write!(out, "<span class=\"{}\">{}", klass.as_html(), text).unwrap();
return Some("</span>");
};
@@ -1009,28 +992,28 @@ fn string_without_closing_tag<T: Display>(
if !open_tag {
// We're already inside an element which has the same klass, no need to give it
// again.
- write!(out, "<a href=\"{}\">{}", href, text_s);
+ write!(out, "<a href=\"{}\">{}", href, text_s).unwrap();
} else {
let klass_s = klass.as_html();
if klass_s.is_empty() {
- write!(out, "<a href=\"{}\">{}", href, text_s);
+ write!(out, "<a href=\"{}\">{}", href, text_s).unwrap();
} else {
- write!(out, "<a class=\"{}\" href=\"{}\">{}", klass_s, href, text_s);
+ write!(out, "<a class=\"{}\" href=\"{}\">{}", klass_s, href, text_s).unwrap();
}
}
return Some("</a>");
}
}
if !open_tag {
- write!(out, "{}", text_s);
+ write!(out, "{}", text_s).unwrap();
return None;
}
let klass_s = klass.as_html();
if klass_s.is_empty() {
- write!(out, "{}", text_s);
+ out.write_str(&text_s).unwrap();
Some("")
} else {
- write!(out, "<span class=\"{}\">{}", klass_s, text_s);
+ write!(out, "<span class=\"{}\">{}", klass_s, text_s).unwrap();
Some("</span>")
}
}
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index fe446ae3c..00aadb8e8 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -36,7 +36,6 @@ use rustc_span::{Span, Symbol};
use once_cell::sync::Lazy;
use std::borrow::Cow;
use std::collections::VecDeque;
-use std::default::Default;
use std::fmt::Write;
use std::ops::{ControlFlow, Range};
use std::str;
@@ -556,7 +555,15 @@ fn check_if_allowed_tag(t: &Tag<'_>) -> bool {
}
fn is_forbidden_tag(t: &Tag<'_>) -> bool {
- matches!(t, Tag::CodeBlock(_) | Tag::Table(_) | Tag::TableHead | Tag::TableRow | Tag::TableCell)
+ matches!(
+ t,
+ Tag::CodeBlock(_)
+ | Tag::Table(_)
+ | Tag::TableHead
+ | Tag::TableRow
+ | Tag::TableCell
+ | Tag::FootnoteDefinition(_)
+ )
}
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for SummaryLine<'a, I> {
@@ -589,6 +596,10 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for SummaryLine<'a, I> {
is_start = false;
check_if_allowed_tag(c)
}
+ Event::FootnoteReference(_) => {
+ self.skipped_tags += 1;
+ false
+ }
_ => true,
};
if !is_allowed_tag {
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 5e4a59562..ac5054ce1 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -17,10 +17,11 @@ use super::print_item::{full_path, item_path, print_item};
use super::search_index::build_index;
use super::write_shared::write_shared;
use super::{
- collect_spans_and_sources, print_sidebar, scrape_examples_help, sidebar_module_like, AllTypes,
- LinkFromSrc, StylePath,
+ collect_spans_and_sources, scrape_examples_help,
+ sidebar::print_sidebar,
+ sidebar::{sidebar_module_like, Sidebar},
+ AllTypes, LinkFromSrc, StylePath,
};
-
use crate::clean::{self, types::ExternalLocation, ExternalCrate};
use crate::config::{ModuleSorting, RenderOptions};
use crate::docfs::{DocFS, PathError};
@@ -35,6 +36,7 @@ use crate::html::url_parts_builder::UrlPartsBuilder;
use crate::html::{layout, sources, static_files};
use crate::scrape_examples::AllCallLocations;
use crate::try_err;
+use askama::Template;
/// Major driving force in all rustdoc rendering. This contains information
/// about where in the tree-like hierarchy rendering is occurring and controls
@@ -350,7 +352,7 @@ impl<'tcx> Context<'tcx> {
},
);
- path = href.into_inner().to_string_lossy().to_string();
+ path = href.into_inner().to_string_lossy().into_owned();
if let Some(c) = path.as_bytes().last() && *c != b'/' {
path.push('/');
@@ -600,17 +602,18 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
};
let all = shared.all.replace(AllTypes::new());
let mut sidebar = Buffer::html();
- if shared.cache.crate_version.is_some() {
- write!(sidebar, "<h2 class=\"location\">Crate {}</h2>", crate_name)
+
+ let blocks = sidebar_module_like(all.item_sections());
+ let bar = Sidebar {
+ title_prefix: "Crate ",
+ title: crate_name.as_str(),
+ is_crate: false,
+ version: "",
+ blocks: vec![blocks],
+ path: String::new(),
};
- let mut items = Buffer::html();
- sidebar_module_like(&mut items, all.item_sections());
- if !items.is_empty() {
- sidebar.push_str("<div class=\"sidebar-elems\">");
- sidebar.push_buffer(items);
- sidebar.push_str("</div>");
- }
+ bar.render_into(&mut sidebar).unwrap();
let v = layout::render(
&shared.layout,
@@ -649,11 +652,35 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
</noscript>\
<link rel=\"stylesheet\" \
href=\"{static_root_path}{settings_css}\">\
- <script defer src=\"{static_root_path}{settings_js}\"></script>",
+ <script defer src=\"{static_root_path}{settings_js}\"></script>\
+ <link rel=\"preload\" href=\"{static_root_path}{theme_light_css}\" \
+ as=\"style\">\
+ <link rel=\"preload\" href=\"{static_root_path}{theme_dark_css}\" \
+ as=\"style\">\
+ <link rel=\"preload\" href=\"{static_root_path}{theme_ayu_css}\" \
+ as=\"style\">",
static_root_path = page.get_static_root_path(),
settings_css = static_files::STATIC_FILES.settings_css,
settings_js = static_files::STATIC_FILES.settings_js,
- )
+ theme_light_css = static_files::STATIC_FILES.theme_light_css,
+ theme_dark_css = static_files::STATIC_FILES.theme_dark_css,
+ theme_ayu_css = static_files::STATIC_FILES.theme_ayu_css,
+ );
+ // Pre-load all theme CSS files, so that switching feels seamless.
+ //
+ // When loading settings.html as a popover, the equivalent HTML is
+ // generated in main.js.
+ for file in &shared.style_files {
+ if let Ok(theme) = file.basename() {
+ write!(
+ buf,
+ "<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
+ as=\"style\">",
+ root_path = page.static_root_path.unwrap_or(""),
+ suffix = page.resource_suffix,
+ );
+ }
+ }
},
&shared.style_files,
);
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index e6a040d02..463184aca 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -30,6 +30,7 @@ mod tests;
mod context;
mod print_item;
+mod sidebar;
mod span_map;
mod write_shared;
@@ -37,7 +38,6 @@ pub(crate) use self::context::*;
pub(crate) use self::span_map::{collect_spans_and_sources, LinkFromSrc};
use std::collections::VecDeque;
-use std::default::Default;
use std::fmt::{self, Write};
use std::fs;
use std::iter::Peekable;
@@ -46,14 +46,14 @@ use std::rc::Rc;
use std::str;
use std::string::ToString;
+use askama::Template;
use rustc_ast_pretty::pprust;
use rustc_attr::{ConstStability, Deprecation, StabilityLevel};
+use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
-use rustc_hir::def::CtorKind;
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_hir::Mutability;
use rustc_middle::middle::stability;
-use rustc_middle::ty;
use rustc_middle::ty::TyCtxt;
use rustc_span::{
symbol::{sym, Symbol},
@@ -69,7 +69,7 @@ use crate::formats::item_type::ItemType;
use crate::formats::{AssocItemRender, Impl, RenderMode};
use crate::html::escape::Escape;
use crate::html::format::{
- href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
+ display_fn, href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space,
Buffer, Ending, HrefError, PrintWithSpace,
};
@@ -104,6 +104,7 @@ pub(crate) struct IndexItem {
pub(crate) parent_idx: Option<usize>,
pub(crate) search_type: Option<IndexItemFunctionType>,
pub(crate) aliases: Box<[Symbol]>,
+ pub(crate) deprecation: Option<Deprecation>,
}
/// A type used for the search index.
@@ -407,149 +408,153 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
)
}
-fn document(
- w: &mut Buffer,
- cx: &mut Context<'_>,
- item: &clean::Item,
- parent: Option<&clean::Item>,
+fn document<'a, 'cx: 'a>(
+ cx: &'a mut Context<'cx>,
+ item: &'a clean::Item,
+ parent: Option<&'a clean::Item>,
heading_offset: HeadingOffset,
-) {
+) -> impl fmt::Display + 'a + Captures<'cx> {
if let Some(ref name) = item.name {
info!("Documenting {}", name);
}
- document_item_info(w, cx, item, parent);
- if parent.is_none() {
- document_full_collapsible(w, item, cx, heading_offset);
- } else {
- document_full(w, item, cx, heading_offset);
- }
+
+ display_fn(move |f| {
+ document_item_info(cx, item, parent).render_into(f).unwrap();
+ if parent.is_none() {
+ write!(f, "{}", document_full_collapsible(item, cx, heading_offset))?;
+ } else {
+ write!(f, "{}", document_full(item, cx, heading_offset))?;
+ }
+ Ok(())
+ })
}
/// Render md_text as markdown.
-fn render_markdown(
- w: &mut Buffer,
- cx: &mut Context<'_>,
- md_text: &str,
+fn render_markdown<'a, 'cx: 'a>(
+ cx: &'a mut Context<'cx>,
+ md_text: &'a str,
links: Vec<RenderedLink>,
heading_offset: HeadingOffset,
-) {
- write!(
- w,
- "<div class=\"docblock\">{}</div>",
- Markdown {
- content: md_text,
- links: &links,
- ids: &mut cx.id_map,
- error_codes: cx.shared.codes,
- edition: cx.shared.edition(),
- playground: &cx.shared.playground,
- heading_offset,
- }
- .into_string()
- )
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ display_fn(move |f| {
+ write!(
+ f,
+ "<div class=\"docblock\">{}</div>",
+ Markdown {
+ content: md_text,
+ links: &links,
+ ids: &mut cx.id_map,
+ error_codes: cx.shared.codes,
+ edition: cx.shared.edition(),
+ playground: &cx.shared.playground,
+ heading_offset,
+ }
+ .into_string()
+ )
+ })
}
/// Writes a documentation block containing only the first paragraph of the documentation. If the
/// docs are longer, a "Read more" link is appended to the end.
-fn document_short(
- w: &mut Buffer,
- item: &clean::Item,
- cx: &mut Context<'_>,
- link: AssocItemLink<'_>,
- parent: &clean::Item,
+fn document_short<'a, 'cx: 'a>(
+ item: &'a clean::Item,
+ cx: &'a mut Context<'cx>,
+ link: AssocItemLink<'a>,
+ parent: &'a clean::Item,
show_def_docs: bool,
-) {
- document_item_info(w, cx, item, Some(parent));
- if !show_def_docs {
- return;
- }
- if let Some(s) = item.doc_value() {
- let (mut summary_html, has_more_content) =
- MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ display_fn(move |f| {
+ document_item_info(cx, item, Some(parent)).render_into(f).unwrap();
+ if !show_def_docs {
+ return Ok(());
+ }
+ if let Some(s) = item.doc_value() {
+ let (mut summary_html, has_more_content) =
+ MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
- if has_more_content {
- let link = format!(r#" <a{}>Read more</a>"#, assoc_href_attr(item, link, cx));
+ if has_more_content {
+ let link = format!(r#" <a{}>Read more</a>"#, assoc_href_attr(item, link, cx));
- if let Some(idx) = summary_html.rfind("</p>") {
- summary_html.insert_str(idx, &link);
- } else {
- summary_html.push_str(&link);
+ if let Some(idx) = summary_html.rfind("</p>") {
+ summary_html.insert_str(idx, &link);
+ } else {
+ summary_html.push_str(&link);
+ }
}
- }
- write!(w, "<div class='docblock'>{}</div>", summary_html,);
- }
+ write!(f, "<div class='docblock'>{}</div>", summary_html)?;
+ }
+ Ok(())
+ })
}
-fn document_full_collapsible(
- w: &mut Buffer,
- item: &clean::Item,
- cx: &mut Context<'_>,
+fn document_full_collapsible<'a, 'cx: 'a>(
+ item: &'a clean::Item,
+ cx: &'a mut Context<'cx>,
heading_offset: HeadingOffset,
-) {
- document_full_inner(w, item, cx, true, heading_offset);
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ document_full_inner(item, cx, true, heading_offset)
}
-fn document_full(
- w: &mut Buffer,
- item: &clean::Item,
- cx: &mut Context<'_>,
+fn document_full<'a, 'cx: 'a>(
+ item: &'a clean::Item,
+ cx: &'a mut Context<'cx>,
heading_offset: HeadingOffset,
-) {
- document_full_inner(w, item, cx, false, heading_offset);
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ document_full_inner(item, cx, false, heading_offset)
}
-fn document_full_inner(
- w: &mut Buffer,
- item: &clean::Item,
- cx: &mut Context<'_>,
+fn document_full_inner<'a, 'cx: 'a>(
+ item: &'a clean::Item,
+ cx: &'a mut Context<'cx>,
is_collapsible: bool,
heading_offset: HeadingOffset,
-) {
- if let Some(s) = item.collapsed_doc_value() {
- debug!("Doc block: =====\n{}\n=====", s);
- if is_collapsible {
- w.write_str(
- "<details class=\"toggle top-doc\" open>\
- <summary class=\"hideme\">\
- <span>Expand description</span>\
- </summary>",
- );
- render_markdown(w, cx, &s, item.links(cx), heading_offset);
- w.write_str("</details>");
- } else {
- render_markdown(w, cx, &s, item.links(cx), heading_offset);
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ display_fn(move |f| {
+ if let Some(s) = item.collapsed_doc_value() {
+ debug!("Doc block: =====\n{}\n=====", s);
+ if is_collapsible {
+ write!(
+ f,
+ "<details class=\"toggle top-doc\" open>\
+ <summary class=\"hideme\">\
+ <span>Expand description</span>\
+ </summary>{}</details>",
+ render_markdown(cx, &s, item.links(cx), heading_offset)
+ )?;
+ } else {
+ write!(f, "{}", render_markdown(cx, &s, item.links(cx), heading_offset))?;
+ }
}
- }
- let kind = match &*item.kind {
- clean::ItemKind::StrippedItem(box kind) | kind => kind,
- };
+ let kind = match &*item.kind {
+ clean::ItemKind::StrippedItem(box kind) | kind => kind,
+ };
- if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind {
- render_call_locations(w, cx, item);
- }
+ if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind {
+ render_call_locations(f, cx, item);
+ }
+ Ok(())
+ })
}
+#[derive(Template)]
+#[template(path = "item_info.html")]
+struct ItemInfo {
+ items: Vec<ShortItemInfo>,
+}
/// Add extra information about an item such as:
///
/// * Stability
/// * Deprecated
/// * Required features (through the `doc_cfg` feature)
fn document_item_info(
- w: &mut Buffer,
cx: &mut Context<'_>,
item: &clean::Item,
parent: Option<&clean::Item>,
-) {
- let item_infos = short_item_info(item, cx, parent);
- if !item_infos.is_empty() {
- w.write_str("<span class=\"item-info\">");
- for info in item_infos {
- w.write_str(&info);
- }
- w.write_str("</span>");
- }
+) -> ItemInfo {
+ let items = short_item_info(item, cx, parent);
+ ItemInfo { items }
}
fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<String> {
@@ -567,7 +572,25 @@ fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<Strin
cfg
);
- Some(format!("<div class=\"stab portability\">{}</div>", cfg?.render_long_html()))
+ Some(cfg?.render_long_html())
+}
+
+#[derive(Template)]
+#[template(path = "short_item_info.html")]
+enum ShortItemInfo {
+ /// A message describing the deprecation of this item
+ Deprecation {
+ message: String,
+ },
+ /// The feature corresponding to an unstable item, and optionally
+ /// a tracking issue URL and number.
+ Unstable {
+ feature: String,
+ tracking: Option<(String, u32)>,
+ },
+ Portability {
+ message: String,
+ },
}
/// Render the stability, deprecation and portability information that is displayed at the top of
@@ -576,7 +599,7 @@ fn short_item_info(
item: &clean::Item,
cx: &mut Context<'_>,
parent: Option<&clean::Item>,
-) -> Vec<String> {
+) -> Vec<ShortItemInfo> {
let mut extra_info = vec![];
if let Some(depr @ Deprecation { note, since, is_since_rustc_version: _, suggestion: _ }) =
@@ -602,15 +625,10 @@ fn short_item_info(
if let Some(note) = note {
let note = note.as_str();
let html = MarkdownItemInfo(note, &mut cx.id_map);
- message.push_str(&format!(": {}", html.into_string()));
- }
- extra_info.push(format!(
- "<div class=\"stab deprecated\">\
- <span class=\"emoji\">👎</span>\
- <span>{}</span>\
- </div>",
- message,
- ));
+ message.push_str(": ");
+ message.push_str(&html.into_string());
+ }
+ extra_info.push(ShortItemInfo::Deprecation { message });
}
// Render unstable items. But don't render "rustc_private" crates (internal compiler crates).
@@ -621,26 +639,17 @@ fn short_item_info(
.filter(|stab| stab.feature != sym::rustc_private)
.map(|stab| (stab.level, stab.feature))
{
- let mut message = "<span class=\"emoji\">🔬</span>\
- <span>This is a nightly-only experimental API."
- .to_owned();
-
- let mut feature = format!("<code>{}</code>", Escape(feature.as_str()));
- if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) {
- feature.push_str(&format!(
- "&nbsp;<a href=\"{url}{issue}\">#{issue}</a>",
- url = url,
- issue = issue
- ));
- }
-
- message.push_str(&format!(" ({})</span>", feature));
-
- extra_info.push(format!("<div class=\"stab unstable\">{}</div>", message));
+ let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue)
+ {
+ Some((url.clone(), issue.get()))
+ } else {
+ None
+ };
+ extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking });
}
- if let Some(portability) = portability(item, parent) {
- extra_info.push(portability);
+ if let Some(message) = portability(item, parent) {
+ extra_info.push(ShortItemInfo::Portability { message });
}
extra_info
@@ -650,7 +659,7 @@ fn short_item_info(
// "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages).
pub(crate) fn render_impls(
cx: &mut Context<'_>,
- w: &mut Buffer,
+ mut w: impl Write,
impls: &[&Impl],
containing_item: &clean::Item,
toggle_open_by_default: bool,
@@ -662,7 +671,7 @@ pub(crate) fn render_impls(
let did = i.trait_did().unwrap();
let provided_trait_methods = i.inner_impl().provided_trait_methods(tcx);
let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods);
- let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() };
+ let mut buffer = Buffer::new();
render_impl(
&mut buffer,
cx,
@@ -683,7 +692,7 @@ pub(crate) fn render_impls(
})
.collect::<Vec<_>>();
rendered_impls.sort();
- w.write_str(&rendered_impls.join(""));
+ w.write_str(&rendered_impls.join("")).unwrap();
}
/// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
@@ -839,7 +848,7 @@ fn assoc_method(
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
header_len += 4;
let indent_str = " ";
- render_attributes_in_pre(w, meth, indent_str);
+ write!(w, "{}", render_attributes_in_pre(meth, indent_str));
(4, indent_str, Ending::NoNewline)
} else {
render_attributes_in_code(w, meth);
@@ -1035,10 +1044,16 @@ fn attributes(it: &clean::Item) -> Vec<String> {
// When an attribute is rendered inside a `<pre>` tag, it is formatted using
// a whitespace prefix and newline.
-fn render_attributes_in_pre(w: &mut Buffer, it: &clean::Item, prefix: &str) {
- for a in attributes(it) {
- writeln!(w, "{}{}", prefix, a);
- }
+fn render_attributes_in_pre<'a>(
+ it: &'a clean::Item,
+ prefix: &'a str,
+) -> impl fmt::Display + Captures<'a> {
+ crate::html::format::display_fn(move |f| {
+ for a in attributes(it) {
+ writeln!(f, "{}{}", prefix, a)?;
+ }
+ Ok(())
+ })
}
// When an attribute is rendered inside a <code> tag, it is formatted using
@@ -1064,61 +1079,68 @@ impl<'a> AssocItemLink<'a> {
}
}
-fn write_impl_section_heading(w: &mut Buffer, title: &str, id: &str) {
+fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) {
write!(
w,
"<h2 id=\"{id}\" class=\"small-section-header\">\
{title}\
<a href=\"#{id}\" class=\"anchor\">§</a>\
</h2>"
- );
+ )
+ .unwrap();
}
pub(crate) fn render_all_impls(
- w: &mut Buffer,
+ mut w: impl Write,
cx: &mut Context<'_>,
containing_item: &clean::Item,
concrete: &[&Impl],
synthetic: &[&Impl],
blanket_impl: &[&Impl],
) {
- let mut impls = Buffer::empty_from(w);
+ let mut impls = Buffer::html();
render_impls(cx, &mut impls, concrete, containing_item, true);
let impls = impls.into_inner();
if !impls.is_empty() {
- write_impl_section_heading(w, "Trait Implementations", "trait-implementations");
- write!(w, "<div id=\"trait-implementations-list\">{}</div>", impls);
+ write_impl_section_heading(&mut w, "Trait Implementations", "trait-implementations");
+ write!(w, "<div id=\"trait-implementations-list\">{}</div>", impls).unwrap();
}
if !synthetic.is_empty() {
- write_impl_section_heading(w, "Auto Trait Implementations", "synthetic-implementations");
- w.write_str("<div id=\"synthetic-implementations-list\">");
- render_impls(cx, w, synthetic, containing_item, false);
- w.write_str("</div>");
+ write_impl_section_heading(
+ &mut w,
+ "Auto Trait Implementations",
+ "synthetic-implementations",
+ );
+ w.write_str("<div id=\"synthetic-implementations-list\">").unwrap();
+ render_impls(cx, &mut w, synthetic, containing_item, false);
+ w.write_str("</div>").unwrap();
}
if !blanket_impl.is_empty() {
- write_impl_section_heading(w, "Blanket Implementations", "blanket-implementations");
- w.write_str("<div id=\"blanket-implementations-list\">");
- render_impls(cx, w, blanket_impl, containing_item, false);
- w.write_str("</div>");
+ write_impl_section_heading(&mut w, "Blanket Implementations", "blanket-implementations");
+ w.write_str("<div id=\"blanket-implementations-list\">").unwrap();
+ render_impls(cx, &mut w, blanket_impl, containing_item, false);
+ w.write_str("</div>").unwrap();
}
}
-fn render_assoc_items(
- w: &mut Buffer,
- cx: &mut Context<'_>,
- containing_item: &clean::Item,
+fn render_assoc_items<'a, 'cx: 'a>(
+ cx: &'a mut Context<'cx>,
+ containing_item: &'a clean::Item,
it: DefId,
- what: AssocItemRender<'_>,
-) {
+ what: AssocItemRender<'a>,
+) -> impl fmt::Display + 'a + Captures<'cx> {
let mut derefs = DefIdSet::default();
derefs.insert(it);
- render_assoc_items_inner(w, cx, containing_item, it, what, &mut derefs)
+ display_fn(move |f| {
+ render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs);
+ Ok(())
+ })
}
fn render_assoc_items_inner(
- w: &mut Buffer,
+ mut w: &mut dyn fmt::Write,
cx: &mut Context<'_>,
containing_item: &clean::Item,
it: DefId,
@@ -1131,7 +1153,7 @@ fn render_assoc_items_inner(
let Some(v) = cache.impls.get(&it) else { return };
let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
if !non_trait.is_empty() {
- let mut tmp_buf = Buffer::empty_from(w);
+ let mut tmp_buf = Buffer::html();
let (render_mode, id) = match what {
AssocItemRender::All => {
write_impl_section_heading(&mut tmp_buf, "Implementations", "implementations");
@@ -1155,7 +1177,7 @@ fn render_assoc_items_inner(
(RenderMode::ForDeref { mut_: deref_mut_ }, cx.derive_id(id))
}
};
- let mut impls_buf = Buffer::empty_from(w);
+ let mut impls_buf = Buffer::html();
for i in &non_trait {
render_impl(
&mut impls_buf,
@@ -1175,10 +1197,10 @@ fn render_assoc_items_inner(
);
}
if !impls_buf.is_empty() {
- w.push_buffer(tmp_buf);
- write!(w, "<div id=\"{}\">", id);
- w.push_buffer(impls_buf);
- w.write_str("</div>");
+ write!(w, "{}", tmp_buf.into_inner()).unwrap();
+ write!(w, "<div id=\"{}\">", id).unwrap();
+ write!(w, "{}", impls_buf.into_inner()).unwrap();
+ w.write_str("</div>").unwrap();
}
}
@@ -1188,7 +1210,7 @@ fn render_assoc_items_inner(
if let Some(impl_) = deref_impl {
let has_deref_mut =
traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait());
- render_deref_methods(w, cx, impl_, containing_item, has_deref_mut, derefs);
+ render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs);
}
// If we were already one level into rendering deref methods, we don't want to render
@@ -1207,7 +1229,7 @@ fn render_assoc_items_inner(
}
fn render_deref_methods(
- w: &mut Buffer,
+ mut w: impl Write,
cx: &mut Context<'_>,
impl_: &Impl,
container_item: &clean::Item,
@@ -1239,10 +1261,10 @@ fn render_deref_methods(
return;
}
}
- render_assoc_items_inner(w, cx, container_item, did, what, derefs);
+ render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs);
} else if let Some(prim) = target.primitive_type() {
if let Some(&did) = cache.primitive_locations.get(&prim) {
- render_assoc_items_inner(w, cx, container_item, did, what, derefs);
+ render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs);
}
}
}
@@ -1291,7 +1313,7 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> O
if let Some(impls) = cx.cache().impls.get(&did) {
for i in impls {
let impl_ = i.inner_impl();
- if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) {
+ if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) {
// Two different types might have the same did,
// without actually being the same.
continue;
@@ -1327,7 +1349,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
for i in impls {
let impl_ = i.inner_impl();
- if !impl_.for_.without_borrowed_ref().is_same(ty.without_borrowed_ref(), cx.cache()) {
+ if !ty.is_doc_subtype_of(&impl_.for_, cx.cache()) {
// Two different types might have the same did,
// without actually being the same.
continue;
@@ -1472,37 +1494,45 @@ fn render_impl(
// We need the stability of the item from the trait
// because impls can't have a stability.
if item.doc_value().is_some() {
- document_item_info(&mut info_buffer, cx, it, Some(parent));
- document_full(&mut doc_buffer, item, cx, HeadingOffset::H5);
+ document_item_info(cx, it, Some(parent))
+ .render_into(&mut info_buffer)
+ .unwrap();
+ write!(
+ &mut doc_buffer,
+ "{}",
+ document_full(item, cx, HeadingOffset::H5)
+ );
short_documented = false;
} else {
// In case the item isn't documented,
// provide short documentation from the trait.
- document_short(
+ write!(
&mut doc_buffer,
- it,
- cx,
- link,
- parent,
- rendering_params.show_def_docs,
+ "{}",
+ document_short(
+ it,
+ cx,
+ link,
+ parent,
+ rendering_params.show_def_docs,
+ )
);
}
}
} else {
- document_item_info(&mut info_buffer, cx, item, Some(parent));
+ document_item_info(cx, item, Some(parent))
+ .render_into(&mut info_buffer)
+ .unwrap();
if rendering_params.show_def_docs {
- document_full(&mut doc_buffer, item, cx, HeadingOffset::H5);
+ write!(&mut doc_buffer, "{}", document_full(item, cx, HeadingOffset::H5));
short_documented = false;
}
}
} else {
- document_short(
+ write!(
&mut doc_buffer,
- item,
- cx,
- link,
- parent,
- rendering_params.show_def_docs,
+ "{}",
+ document_short(item, cx, link, parent, rendering_params.show_def_docs,)
);
}
}
@@ -1862,161 +1892,17 @@ pub(crate) fn render_impl_summary(
let is_trait = inner_impl.trait_.is_some();
if is_trait {
if let Some(portability) = portability(&i.impl_item, Some(parent)) {
- write!(w, "<span class=\"item-info\">{}</span>", portability);
+ write!(
+ w,
+ "<span class=\"item-info\"><div class=\"stab portability\">{}</div></span>",
+ portability
+ );
}
}
w.write_str("</section>");
}
-fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
- if it.is_struct()
- || it.is_trait()
- || it.is_primitive()
- || it.is_union()
- || it.is_enum()
- || it.is_mod()
- || it.is_typedef()
- {
- write!(
- buffer,
- "<h2 class=\"location\"><a href=\"#\">{}{}</a></h2>",
- match *it.kind {
- clean::ModuleItem(..) =>
- if it.is_crate() {
- "Crate "
- } else {
- "Module "
- },
- _ => "",
- },
- it.name.as_ref().unwrap()
- );
- }
-
- buffer.write_str("<div class=\"sidebar-elems\">");
- if it.is_crate() {
- write!(buffer, "<ul class=\"block\">");
- if let Some(ref version) = cx.cache().crate_version {
- write!(buffer, "<li class=\"version\">Version {}</li>", Escape(version));
- }
- write!(buffer, "<li><a id=\"all-types\" href=\"all.html\">All Items</a></li>");
- buffer.write_str("</ul>");
- }
-
- match *it.kind {
- clean::StructItem(ref s) => sidebar_struct(cx, buffer, it, s),
- clean::TraitItem(ref t) => sidebar_trait(cx, buffer, it, t),
- clean::PrimitiveItem(_) => sidebar_primitive(cx, buffer, it),
- clean::UnionItem(ref u) => sidebar_union(cx, buffer, it, u),
- clean::EnumItem(ref e) => sidebar_enum(cx, buffer, it, e),
- clean::TypedefItem(_) => sidebar_typedef(cx, buffer, it),
- clean::ModuleItem(ref m) => sidebar_module(buffer, &m.items),
- clean::ForeignTypeItem => sidebar_foreign_type(cx, buffer, it),
- _ => {}
- }
-
- // The sidebar is designed to display sibling functions, modules and
- // other miscellaneous information. since there are lots of sibling
- // items (and that causes quadratic growth in large modules),
- // we refactor common parts into a shared JavaScript file per module.
- // still, we don't move everything into JS because we want to preserve
- // as much HTML as possible in order to allow non-JS-enabled browsers
- // to navigate the documentation (though slightly inefficiently).
-
- if !it.is_mod() {
- let path: String = cx.current.iter().map(|s| s.as_str()).intersperse("::").collect();
-
- write!(buffer, "<h2><a href=\"index.html\">In {}</a></h2>", path);
- }
-
- // Closes sidebar-elems div.
- buffer.write_str("</div>");
-}
-
-fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String {
- if used_links.insert(url.clone()) {
- return url;
- }
- let mut add = 1;
- while !used_links.insert(format!("{}-{}", url, add)) {
- add += 1;
- }
- format!("{}-{}", url, add)
-}
-
-struct SidebarLink {
- name: Symbol,
- url: String,
-}
-
-impl fmt::Display for SidebarLink {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "<a href=\"#{}\">{}</a>", self.url, self.name)
- }
-}
-
-impl PartialEq for SidebarLink {
- fn eq(&self, other: &Self) -> bool {
- self.url == other.url
- }
-}
-
-impl Eq for SidebarLink {}
-
-impl PartialOrd for SidebarLink {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl Ord for SidebarLink {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.url.cmp(&other.url)
- }
-}
-
-fn get_methods(
- i: &clean::Impl,
- for_deref: bool,
- used_links: &mut FxHashSet<String>,
- deref_mut: bool,
- tcx: TyCtxt<'_>,
-) -> Vec<SidebarLink> {
- i.items
- .iter()
- .filter_map(|item| match item.name {
- Some(name) if !name.is_empty() && item.is_method() => {
- if !for_deref || should_render_item(item, deref_mut, tcx) {
- Some(SidebarLink {
- name,
- url: get_next_url(used_links, format!("{}.{}", ItemType::Method, name)),
- })
- } else {
- None
- }
- }
- _ => None,
- })
- .collect::<Vec<_>>()
-}
-
-fn get_associated_constants(
- i: &clean::Impl,
- used_links: &mut FxHashSet<String>,
-) -> Vec<SidebarLink> {
- i.items
- .iter()
- .filter_map(|item| match item.name {
- Some(name) if !name.is_empty() && item.is_associated_const() => Some(SidebarLink {
- name,
- url: get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)),
- }),
- _ => None,
- })
- .collect::<Vec<_>>()
-}
-
pub(crate) fn small_url_encode(s: String) -> String {
// These characters don't need to be escaped in a URI.
// See https://url.spec.whatwg.org/#query-percent-encode-set
@@ -2082,232 +1968,6 @@ pub(crate) fn small_url_encode(s: String) -> String {
}
}
-pub(crate) fn sidebar_render_assoc_items(
- cx: &Context<'_>,
- out: &mut Buffer,
- id_map: &mut IdMap,
- concrete: Vec<&Impl>,
- synthetic: Vec<&Impl>,
- blanket_impl: Vec<&Impl>,
-) {
- let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| {
- let mut links = FxHashSet::default();
-
- let mut ret = impls
- .iter()
- .filter_map(|it| {
- let trait_ = it.inner_impl().trait_.as_ref()?;
- let encoded =
- id_map.derive(get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx));
-
- let i_display = format!("{:#}", trait_.print(cx));
- let out = Escape(&i_display);
- let prefix = match it.inner_impl().polarity {
- ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "",
- ty::ImplPolarity::Negative => "!",
- };
- let generated = format!("<a href=\"#{}\">{}{}</a>", encoded, prefix, out);
- if links.insert(generated.clone()) { Some(generated) } else { None }
- })
- .collect::<Vec<String>>();
- ret.sort();
- ret
- };
-
- let concrete_format = format_impls(concrete, id_map);
- let synthetic_format = format_impls(synthetic, id_map);
- let blanket_format = format_impls(blanket_impl, id_map);
-
- if !concrete_format.is_empty() {
- print_sidebar_block(
- out,
- "trait-implementations",
- "Trait Implementations",
- concrete_format.iter(),
- );
- }
-
- if !synthetic_format.is_empty() {
- print_sidebar_block(
- out,
- "synthetic-implementations",
- "Auto Trait Implementations",
- synthetic_format.iter(),
- );
- }
-
- if !blanket_format.is_empty() {
- print_sidebar_block(
- out,
- "blanket-implementations",
- "Blanket Implementations",
- blanket_format.iter(),
- );
- }
-}
-
-fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) {
- let did = it.item_id.expect_def_id();
- let cache = cx.cache();
-
- if let Some(v) = cache.impls.get(&did) {
- let mut used_links = FxHashSet::default();
- let mut id_map = IdMap::new();
-
- {
- let used_links_bor = &mut used_links;
- let mut assoc_consts = v
- .iter()
- .filter(|i| i.inner_impl().trait_.is_none())
- .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor))
- .collect::<Vec<_>>();
- if !assoc_consts.is_empty() {
- // We want links' order to be reproducible so we don't use unstable sort.
- assoc_consts.sort();
-
- print_sidebar_block(
- out,
- "implementations",
- "Associated Constants",
- assoc_consts.iter(),
- );
- }
- let mut methods = v
- .iter()
- .filter(|i| i.inner_impl().trait_.is_none())
- .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx()))
- .collect::<Vec<_>>();
- if !methods.is_empty() {
- // We want links' order to be reproducible so we don't use unstable sort.
- methods.sort();
-
- print_sidebar_block(out, "implementations", "Methods", methods.iter());
- }
- }
-
- if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
- if let Some(impl_) =
- v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait())
- {
- let mut derefs = DefIdSet::default();
- derefs.insert(did);
- sidebar_deref_methods(cx, out, impl_, v, &mut derefs, &mut used_links);
- }
-
- let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
- v.iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_auto());
- let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) =
- concrete.into_iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_blanket());
-
- sidebar_render_assoc_items(cx, out, &mut id_map, concrete, synthetic, blanket_impl);
- }
- }
-}
-
-fn sidebar_deref_methods(
- cx: &Context<'_>,
- out: &mut Buffer,
- impl_: &Impl,
- v: &[Impl],
- derefs: &mut DefIdSet,
- used_links: &mut FxHashSet<String>,
-) {
- let c = cx.cache();
-
- debug!("found Deref: {:?}", impl_);
- if let Some((target, real_target)) =
- impl_.inner_impl().items.iter().find_map(|item| match *item.kind {
- clean::AssocTypeItem(box ref t, _) => Some(match *t {
- clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_),
- _ => (&t.type_, &t.type_),
- }),
- _ => None,
- })
- {
- debug!("found target, real_target: {:?} {:?}", target, real_target);
- if let Some(did) = target.def_id(c) &&
- let Some(type_did) = impl_.inner_impl().for_.def_id(c) &&
- // `impl Deref<Target = S> for S`
- (did == type_did || !derefs.insert(did))
- {
- // Avoid infinite cycles
- return;
- }
- let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait());
- let inner_impl = target
- .def_id(c)
- .or_else(|| {
- target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned())
- })
- .and_then(|did| c.impls.get(&did));
- if let Some(impls) = inner_impl {
- debug!("found inner_impl: {:?}", impls);
- let mut ret = impls
- .iter()
- .filter(|i| i.inner_impl().trait_.is_none())
- .flat_map(|i| get_methods(i.inner_impl(), true, used_links, deref_mut, cx.tcx()))
- .collect::<Vec<_>>();
- if !ret.is_empty() {
- let id = if let Some(target_def_id) = real_target.def_id(c) {
- cx.deref_id_map.get(&target_def_id).expect("Deref section without derived id")
- } else {
- "deref-methods"
- };
- let title = format!(
- "Methods from {}&lt;Target={}&gt;",
- Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))),
- Escape(&format!("{:#}", real_target.print(cx))),
- );
- // We want links' order to be reproducible so we don't use unstable sort.
- ret.sort();
- print_sidebar_block(out, id, &title, ret.iter());
- }
- }
-
- // 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| {
- i.inner_impl()
- .trait_
- .as_ref()
- .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait())
- .unwrap_or(false)
- })
- {
- sidebar_deref_methods(
- cx,
- out,
- target_deref_impl,
- target_impls,
- derefs,
- used_links,
- );
- }
- }
-}
-
-fn sidebar_struct(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, s: &clean::Struct) {
- let mut sidebar = Buffer::new();
- let fields = get_struct_fields_name(&s.fields);
-
- if !fields.is_empty() {
- match s.ctor_kind {
- None => {
- print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter());
- }
- Some(CtorKind::Fn) => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"),
- Some(CtorKind::Const) => {}
- }
- }
-
- sidebar_assoc_items(cx, &mut sidebar, it);
-
- if !sidebar.is_empty() {
- write!(buf, "<section>{}</section>", sidebar.into_inner());
- }
-}
-
fn get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String {
match trait_ {
Some(t) => small_url_encode(format!("impl-{:#}-for-{:#}", t.print(cx), for_.print(cx))),
@@ -2328,131 +1988,6 @@ fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String
}
}
-fn print_sidebar_title(buf: &mut Buffer, id: &str, title: &str) {
- write!(buf, "<h3><a href=\"#{}\">{}</a></h3>", id, title);
-}
-
-fn print_sidebar_block(
- buf: &mut Buffer,
- id: &str,
- title: &str,
- items: impl Iterator<Item = impl fmt::Display>,
-) {
- print_sidebar_title(buf, id, title);
- buf.push_str("<ul class=\"block\">");
- for item in items {
- write!(buf, "<li>{}</li>", item);
- }
- buf.push_str("</ul>");
-}
-
-fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean::Trait) {
- buf.write_str("<section>");
-
- fn print_sidebar_section(
- out: &mut Buffer,
- items: &[clean::Item],
- id: &str,
- title: &str,
- filter: impl Fn(&clean::Item) -> bool,
- mapper: impl Fn(&str) -> String,
- ) {
- let mut items: Vec<&str> = items
- .iter()
- .filter_map(|m| match m.name {
- Some(ref name) if filter(m) => Some(name.as_str()),
- _ => None,
- })
- .collect::<Vec<_>>();
-
- if !items.is_empty() {
- items.sort_unstable();
- print_sidebar_block(out, id, title, items.into_iter().map(mapper));
- }
- }
-
- print_sidebar_section(
- buf,
- &t.items,
- "required-associated-types",
- "Required Associated Types",
- |m| m.is_ty_associated_type(),
- |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocType),
- );
-
- print_sidebar_section(
- buf,
- &t.items,
- "provided-associated-types",
- "Provided Associated Types",
- |m| m.is_associated_type(),
- |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocType),
- );
-
- print_sidebar_section(
- buf,
- &t.items,
- "required-associated-consts",
- "Required Associated Constants",
- |m| m.is_ty_associated_const(),
- |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocConst),
- );
-
- print_sidebar_section(
- buf,
- &t.items,
- "provided-associated-consts",
- "Provided Associated Constants",
- |m| m.is_associated_const(),
- |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocConst),
- );
-
- print_sidebar_section(
- buf,
- &t.items,
- "required-methods",
- "Required Methods",
- |m| m.is_ty_method(),
- |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::TyMethod),
- );
-
- print_sidebar_section(
- buf,
- &t.items,
- "provided-methods",
- "Provided Methods",
- |m| m.is_method(),
- |sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::Method),
- );
-
- if let Some(implementors) = cx.cache().implementors.get(&it.item_id.expect_def_id()) {
- let mut res = implementors
- .iter()
- .filter(|i| !i.is_on_local_type(cx))
- .filter_map(|i| extract_for_impl_name(&i.impl_item, cx))
- .collect::<Vec<_>>();
-
- if !res.is_empty() {
- res.sort();
- print_sidebar_block(
- buf,
- "foreign-impls",
- "Implementations on Foreign Types",
- res.iter().map(|(name, id)| format!("<a href=\"#{}\">{}</a>", id, Escape(name))),
- );
- }
- }
-
- sidebar_assoc_items(cx, buf, it);
-
- print_sidebar_title(buf, "implementors", "Implementors");
- if t.is_auto(cx.tcx()) {
- print_sidebar_title(buf, "synthetic-implementors", "Auto Implementors");
- }
-
- buf.push_str("</section>")
-}
-
/// Returns the list of implementations for the primitive reference type, filtering out any
/// implementations that are on concrete or partially generic types, only keeping implementations
/// of the form `impl<T> Trait for &T`.
@@ -2483,89 +2018,6 @@ pub(crate) fn get_filtered_impls_for_reference<'a>(
(concrete, synthetic, blanket_impl)
}
-fn sidebar_primitive(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
- let mut sidebar = Buffer::new();
-
- if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
- sidebar_assoc_items(cx, &mut sidebar, it);
- } else {
- let shared = Rc::clone(&cx.shared);
- let (concrete, synthetic, blanket_impl) = get_filtered_impls_for_reference(&shared, it);
-
- sidebar_render_assoc_items(
- cx,
- &mut sidebar,
- &mut IdMap::new(),
- concrete,
- synthetic,
- blanket_impl,
- );
- }
-
- if !sidebar.is_empty() {
- write!(buf, "<section>{}</section>", sidebar.into_inner());
- }
-}
-
-fn sidebar_typedef(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
- let mut sidebar = Buffer::new();
- sidebar_assoc_items(cx, &mut sidebar, it);
-
- if !sidebar.is_empty() {
- write!(buf, "<section>{}</section>", sidebar.into_inner());
- }
-}
-
-fn get_struct_fields_name(fields: &[clean::Item]) -> Vec<String> {
- let mut fields = fields
- .iter()
- .filter(|f| matches!(*f.kind, clean::StructFieldItem(..)))
- .filter_map(|f| {
- f.name.map(|name| format!("<a href=\"#structfield.{name}\">{name}</a>", name = name))
- })
- .collect::<Vec<_>>();
- fields.sort();
- fields
-}
-
-fn sidebar_union(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, u: &clean::Union) {
- let mut sidebar = Buffer::new();
- let fields = get_struct_fields_name(&u.fields);
-
- if !fields.is_empty() {
- print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter());
- }
-
- sidebar_assoc_items(cx, &mut sidebar, it);
-
- if !sidebar.is_empty() {
- write!(buf, "<section>{}</section>", sidebar.into_inner());
- }
-}
-
-fn sidebar_enum(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, e: &clean::Enum) {
- let mut sidebar = Buffer::new();
-
- let mut variants = e
- .variants()
- .filter_map(|v| {
- v.name
- .as_ref()
- .map(|name| format!("<a href=\"#variant.{name}\">{name}</a>", name = name))
- })
- .collect::<Vec<_>>();
- if !variants.is_empty() {
- variants.sort_unstable();
- print_sidebar_block(&mut sidebar, "variants", "Variants", variants.iter());
- }
-
- sidebar_assoc_items(cx, &mut sidebar, it);
-
- if !sidebar.is_empty() {
- write!(buf, "<section>{}</section>", sidebar.into_inner());
- }
-}
-
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum ItemSection {
Reexports,
@@ -2719,54 +2171,6 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection {
}
}
-pub(crate) fn sidebar_module_like(buf: &mut Buffer, item_sections_in_use: FxHashSet<ItemSection>) {
- use std::fmt::Write as _;
-
- let mut sidebar = String::new();
-
- for &sec in ItemSection::ALL.iter().filter(|sec| item_sections_in_use.contains(sec)) {
- let _ = write!(sidebar, "<li><a href=\"#{}\">{}</a></li>", sec.id(), sec.name());
- }
-
- if !sidebar.is_empty() {
- write!(
- buf,
- "<section>\
- <ul class=\"block\">{}</ul>\
- </section>",
- sidebar
- );
- }
-}
-
-fn sidebar_module(buf: &mut Buffer, items: &[clean::Item]) {
- let item_sections_in_use: FxHashSet<_> = items
- .iter()
- .filter(|it| {
- !it.is_stripped()
- && it
- .name
- .or_else(|| {
- if let clean::ImportItem(ref i) = *it.kind &&
- let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None }
- })
- .is_some()
- })
- .map(|it| item_ty_to_section(it.type_()))
- .collect();
-
- sidebar_module_like(buf, item_sections_in_use);
-}
-
-fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
- let mut sidebar = Buffer::new();
- sidebar_assoc_items(cx, &mut sidebar, it);
-
- if !sidebar.is_empty() {
- write!(buf, "<section>{}</section>", sidebar.into_inner());
- }
-}
-
/// Returns a list of all paths used in the type.
/// This is used to help deduplicate imported impls
/// for reexported types. If any of the contained
@@ -2825,7 +2229,7 @@ const MAX_FULL_EXAMPLES: usize = 5;
const NUM_VISIBLE_LINES: usize = 10;
/// Generates the HTML for example call locations generated via the --scrape-examples flag.
-fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item) {
+fn render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &clean::Item) {
let tcx = cx.tcx();
let def_id = item.item_id.expect_def_id();
let key = tcx.def_path_hash(def_id);
@@ -2834,7 +2238,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
// Generate a unique ID so users can link to this section for a given method
let id = cx.id_map.derive("scraped-examples");
write!(
- w,
+ &mut w,
"<div class=\"docblock scraped-example-list\">\
<span></span>\
<h5 id=\"{id}\">\
@@ -2843,7 +2247,8 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
</h5>",
root_path = cx.root_path(),
id = id
- );
+ )
+ .unwrap();
// Create a URL to a particular location in a reverse-dependency's source file
let link_to_loc = |call_data: &CallData, loc: &CallLocation| -> (String, String) {
@@ -2861,7 +2266,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
};
// Generate the HTML for a single example, being the title and code block
- let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| -> bool {
+ let write_example = |mut w: &mut W, (path, call_data): (&PathBuf, &CallData)| -> bool {
let contents = match fs::read_to_string(&path) {
Ok(contents) => contents,
Err(err) => {
@@ -2909,7 +2314,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
let locations_encoded = serde_json::to_string(&line_ranges).unwrap();
write!(
- w,
+ &mut w,
"<div class=\"scraped-example {expanded_cls}\" data-locs=\"{locations}\">\
<div class=\"scraped-example-title\">\
{name} (<a href=\"{url}\">{title}</a>)\
@@ -2922,10 +2327,12 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
// The locations are encoded as a data attribute, so they can be read
// later by the JS for interactions.
locations = Escape(&locations_encoded)
- );
+ )
+ .unwrap();
if line_ranges.len() > 1 {
- write!(w, r#"<button class="prev">&pr;</button> <button class="next">&sc;</button>"#);
+ write!(w, r#"<button class="prev">&pr;</button> <button class="next">&sc;</button>"#)
+ .unwrap();
}
// Look for the example file in the source map if it exists, otherwise return a dummy span
@@ -2952,7 +2359,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
decoration_info.insert("highlight", byte_ranges);
sources::print_src(
- w,
+ &mut w,
contents_subset,
file_span,
cx,
@@ -2960,7 +2367,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
highlight::DecorationInfo(decoration_info),
sources::SourceContext::Embedded { offset: line_min, needs_expansion },
);
- write!(w, "</div></div>");
+ write!(w, "</div></div>").unwrap();
true
};
@@ -2994,7 +2401,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
// An example may fail to write if its source can't be read for some reason, so this method
// continues iterating until a write succeeds
- let write_and_skip_failure = |w: &mut Buffer, it: &mut Peekable<_>| {
+ let write_and_skip_failure = |w: &mut W, it: &mut Peekable<_>| {
while let Some(example) = it.next() {
if write_example(&mut *w, example) {
break;
@@ -3003,7 +2410,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
};
// Write just one example that's visible by default in the method's description.
- write_and_skip_failure(w, &mut it);
+ write_and_skip_failure(&mut w, &mut it);
// Then add the remaining examples in a hidden section.
if it.peek().is_some() {
@@ -3016,17 +2423,19 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
<div class=\"hide-more\">Hide additional examples</div>\
<div class=\"more-scraped-examples\">\
<div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>"
- );
+ )
+ .unwrap();
// Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could
// make the page arbitrarily huge!
for _ in 0..MAX_FULL_EXAMPLES {
- write_and_skip_failure(w, &mut it);
+ write_and_skip_failure(&mut w, &mut it);
}
// For the remaining examples, generate a <ul> containing links to the source files.
if it.peek().is_some() {
- write!(w, r#"<div class="example-links">Additional examples can be found in:<br><ul>"#);
+ write!(w, r#"<div class="example-links">Additional examples can be found in:<br><ul>"#)
+ .unwrap();
it.for_each(|(_, call_data)| {
let (url, _) = link_to_loc(call_data, &call_data.locations[0]);
write!(
@@ -3034,13 +2443,14 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
r#"<li><a href="{url}">{name}</a></li>"#,
url = url,
name = call_data.display_name
- );
+ )
+ .unwrap();
});
- write!(w, "</ul></div>");
+ write!(w, "</ul></div>").unwrap();
}
- write!(w, "</div></details>");
+ write!(w, "</div></details>").unwrap();
}
- write!(w, "</div>");
+ write!(w, "</div>").unwrap();
}
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 2869a3961..9a968e48b 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1,5 +1,6 @@
use clean::AttributesExt;
+use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
use rustc_hir::def::CtorKind;
@@ -28,8 +29,8 @@ use crate::formats::item_type::ItemType;
use crate::formats::{AssocItemRender, Impl, RenderMode};
use crate::html::escape::Escape;
use crate::html::format::{
- join_with_double_colon, print_abi_with_space, print_constness_with_space, print_where_clause,
- visibility_print_with_space, Buffer, Ending, PrintWithSpace,
+ display_fn, join_with_double_colon, print_abi_with_space, print_constness_with_space,
+ print_where_clause, visibility_print_with_space, Buffer, Ending, PrintWithSpace,
};
use crate::html::layout::Page;
use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
@@ -201,7 +202,7 @@ fn should_hide_fields(n_fields: usize) -> bool {
n_fields > 12
}
-fn toggle_open(w: &mut Buffer, text: impl fmt::Display) {
+fn toggle_open(mut w: impl fmt::Write, text: impl fmt::Display) {
write!(
w,
"<details class=\"toggle type-contents-toggle\">\
@@ -209,15 +210,16 @@ fn toggle_open(w: &mut Buffer, text: impl fmt::Display) {
<span>Show {}</span>\
</summary>",
text
- );
+ )
+ .unwrap();
}
-fn toggle_close(w: &mut Buffer) {
- w.write_str("</details>");
+fn toggle_close(mut w: impl fmt::Write) {
+ w.write_str("</details>").unwrap();
}
fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) {
- document(w, cx, item, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, item, None, HeadingOffset::H2));
let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::<Vec<usize>>();
@@ -367,7 +369,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
..myitem.clone()
};
- let stab_tags = Some(extra_info_tags(&import_item, item, cx.tcx()));
+ let stab_tags = Some(extra_info_tags(&import_item, item, cx.tcx()).to_string());
stab_tags
} else {
None
@@ -461,41 +463,62 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
/// Render the stability, deprecation and portability tags that are displayed in the item's summary
/// at the module level.
-fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> String {
- let mut tags = String::new();
-
- fn tag_html(class: &str, title: &str, contents: &str) -> String {
- format!(r#"<span class="stab {}" title="{}">{}</span>"#, class, Escape(title), contents)
- }
-
- // The trailing space after each tag is to space it properly against the rest of the docs.
- if let Some(depr) = &item.deprecation(tcx) {
- let mut message = "Deprecated";
- if !stability::deprecation_in_effect(depr) {
- message = "Deprecation planned";
+fn extra_info_tags<'a, 'tcx: 'a>(
+ item: &'a clean::Item,
+ parent: &'a clean::Item,
+ tcx: TyCtxt<'tcx>,
+) -> impl fmt::Display + 'a + Captures<'tcx> {
+ display_fn(move |f| {
+ fn tag_html<'a>(
+ class: &'a str,
+ title: &'a str,
+ contents: &'a str,
+ ) -> impl fmt::Display + 'a {
+ display_fn(move |f| {
+ write!(
+ f,
+ r#"<span class="stab {}" title="{}">{}</span>"#,
+ class,
+ Escape(title),
+ contents
+ )
+ })
}
- tags += &tag_html("deprecated", "", message);
- }
- // 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)
- {
- tags += &tag_html("unstable", "", "Experimental");
- }
+ // The trailing space after each tag is to space it properly against the rest of the docs.
+ if let Some(depr) = &item.deprecation(tcx) {
+ let message = if stability::deprecation_in_effect(depr) {
+ "Deprecated"
+ } else {
+ "Deprecation planned"
+ };
+ write!(f, "{}", tag_html("deprecated", "", message))?;
+ }
- let cfg = match (&item.cfg, parent.cfg.as_ref()) {
- (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg),
- (cfg, _) => cfg.as_deref().cloned(),
- };
+ // 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)
+ {
+ write!(f, "{}", tag_html("unstable", "", "Experimental"))?;
+ }
- debug!("Portability name={:?} {:?} - {:?} = {:?}", item.name, item.cfg, parent.cfg, cfg);
- if let Some(ref cfg) = cfg {
- tags += &tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html());
- }
+ let cfg = match (&item.cfg, parent.cfg.as_ref()) {
+ (Some(cfg), Some(parent_cfg)) => cfg.simplify_with(parent_cfg),
+ (cfg, _) => cfg.as_deref().cloned(),
+ };
- tags
+ debug!("Portability name={:?} {:?} - {:?} = {:?}", item.name, item.cfg, parent.cfg, cfg);
+ if let Some(ref cfg) = cfg {
+ write!(
+ f,
+ "{}",
+ tag_html("portability", &cfg.render_long_plain(), &cfg.render_short_html())
+ )
+ } else {
+ Ok(())
+ }
+ })
}
fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &clean::Function) {
@@ -522,12 +545,12 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx));
wrap_item(w, |w| {
- render_attributes_in_pre(w, it, "");
w.reserve(header_len);
write!(
w,
- "{vis}{constness}{asyncness}{unsafety}{abi}fn \
+ "{attrs}{vis}{constness}{asyncness}{unsafety}{abi}fn \
{name}{generics}{decl}{notable_traits}{where_clause}",
+ attrs = render_attributes_in_pre(it, ""),
vis = visibility,
constness = constness,
asyncness = asyncness,
@@ -540,7 +563,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
notable_traits = notable_traits.unwrap_or_default(),
);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
}
fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Trait) {
@@ -558,17 +581,17 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
let must_implement_one_of_functions = tcx.trait_def(t.def_id).must_implement_one_of.clone();
// Output the trait definition
- wrap_item(w, |w| {
- render_attributes_in_pre(w, it, "");
+ wrap_item(w, |mut w| {
write!(
w,
- "{}{}{}trait {}{}{}",
+ "{attrs}{}{}{}trait {}{}{}",
visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
t.unsafety(tcx).print_with_space(),
if t.is_auto(tcx) { "auto " } else { "" },
it.name.unwrap(),
t.generics.print(cx),
- bounds
+ bounds,
+ attrs = render_attributes_in_pre(it, ""),
);
if !t.generics.where_predicates.is_empty() {
@@ -588,7 +611,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
if should_hide_fields(count_types) {
toggle = true;
toggle_open(
- w,
+ &mut w,
format_args!("{} associated items", count_types + count_consts + count_methods),
);
}
@@ -612,7 +635,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
if !toggle && should_hide_fields(count_types + count_consts) {
toggle = true;
toggle_open(
- w,
+ &mut w,
format_args!(
"{} associated constant{} and {} method{}",
count_consts,
@@ -640,7 +663,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
}
if !toggle && should_hide_fields(count_methods) {
toggle = true;
- toggle_open(w, format_args!("{} methods", count_methods));
+ toggle_open(&mut w, format_args!("{} methods", count_methods));
}
if count_consts != 0 && count_methods != 0 {
w.write_str("\n");
@@ -688,14 +711,14 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
}
}
if toggle {
- toggle_close(w);
+ toggle_close(&mut w);
}
w.write_str("}");
}
});
// Trait documentation
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) {
write!(
@@ -713,7 +736,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
let item_type = m.type_();
let id = cx.derive_id(format!("{}.{}", item_type, name));
let mut content = Buffer::empty_from(w);
- document(&mut content, cx, m, Some(t), HeadingOffset::H5);
+ write!(&mut content, "{}", document(cx, m, Some(t), HeadingOffset::H5));
let toggled = !content.is_empty();
if toggled {
let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" };
@@ -825,7 +848,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
}
// If there are methods directly on this trait object, render them here.
- render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All);
+ write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All));
let cloned_shared = Rc::clone(&cx.shared);
let cache = &cloned_shared.cache;
@@ -858,8 +881,8 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) =
local.iter().partition(|i| i.inner_impl().kind.is_auto());
- synthetic.sort_by(|a, b| compare_impl(a, b, cx));
- concrete.sort_by(|a, b| compare_impl(a, b, cx));
+ synthetic.sort_by_cached_key(|i| ImplString::new(i, cx));
+ concrete.sort_by_cached_key(|i| ImplString::new(i, cx));
if !foreign.is_empty() {
write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", "");
@@ -1035,147 +1058,201 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::TraitAlias) {
wrap_item(w, |w| {
- render_attributes_in_pre(w, it, "");
write!(
w,
- "trait {}{}{} = {};",
+ "{attrs}trait {}{}{} = {};",
it.name.unwrap(),
t.generics.print(cx),
print_where_clause(&t.generics, cx, 0, Ending::Newline),
- bounds(&t.bounds, true, cx)
+ bounds(&t.bounds, true, cx),
+ attrs = render_attributes_in_pre(it, ""),
);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
// associated items from the aliased type (see discussion in #32077), but
// we need #14072 to make sense of the generics.
- render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All)
+ write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
}
fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) {
wrap_item(w, |w| {
- render_attributes_in_pre(w, it, "");
write!(
w,
- "type {}{}{where_clause} = impl {bounds};",
+ "{attrs}type {}{}{where_clause} = impl {bounds};",
it.name.unwrap(),
t.generics.print(cx),
where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
bounds = bounds(&t.bounds, false, cx),
+ attrs = render_attributes_in_pre(it, ""),
);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
// associated items from the aliased type (see discussion in #32077), but
// we need #14072 to make sense of the generics.
- render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All)
+ write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
}
fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Typedef) {
fn write_content(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) {
wrap_item(w, |w| {
- render_attributes_in_pre(w, it, "");
write!(
w,
- "{}type {}{}{where_clause} = {type_};",
+ "{attrs}{}type {}{}{where_clause} = {type_};",
visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx),
it.name.unwrap(),
t.generics.print(cx),
where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
type_ = t.type_.print(cx),
+ attrs = render_attributes_in_pre(it, ""),
);
});
}
write_content(w, cx, it, t);
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
let def_id = it.item_id.expect_def_id();
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
// associated items from the aliased type (see discussion in #32077), but
// we need #14072 to make sense of the generics.
- render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
- document_type_layout(w, cx, def_id);
+ write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
+ write!(w, "{}", document_type_layout(cx, def_id));
}
fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) {
- wrap_item(w, |w| {
- render_attributes_in_pre(w, it, "");
- render_union(w, it, Some(&s.generics), &s.fields, cx);
- });
+ #[derive(Template)]
+ #[template(path = "item_union.html")]
+ struct ItemUnion<'a, 'cx> {
+ cx: std::cell::RefCell<&'a mut Context<'cx>>,
+ it: &'a clean::Item,
+ s: &'a clean::Union,
+ }
- document(w, cx, it, None, HeadingOffset::H2);
+ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
+ fn render_assoc_items<'b>(
+ &'b self,
+ ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let def_id = self.it.item_id.expect_def_id();
+ let mut cx = self.cx.borrow_mut();
+ let v = render_assoc_items(*cx, self.it, def_id, AssocItemRender::All);
+ write!(f, "{v}")
+ })
+ }
+ fn document_type_layout<'b>(
+ &'b self,
+ ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let def_id = self.it.item_id.expect_def_id();
+ let cx = self.cx.borrow_mut();
+ let v = document_type_layout(*cx, def_id);
+ write!(f, "{v}")
+ })
+ }
+ fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let cx = self.cx.borrow_mut();
+ let v = render_union(self.it, Some(&self.s.generics), &self.s.fields, *cx);
+ write!(f, "{v}")
+ })
+ }
+ fn render_attributes_in_pre<'b>(
+ &'b self,
+ ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let v = render_attributes_in_pre(self.it, "");
+ write!(f, "{v}")
+ })
+ }
+ fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let mut cx = self.cx.borrow_mut();
+ let v = document(*cx, self.it, None, HeadingOffset::H2);
+ write!(f, "{v}")
+ })
+ }
+ fn document_field<'b>(
+ &'b self,
+ field: &'a clean::Item,
+ ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let mut cx = self.cx.borrow_mut();
+ let v = document(*cx, field, Some(self.it), HeadingOffset::H3);
+ write!(f, "{v}")
+ })
+ }
+ fn stability_field(&self, field: &clean::Item) -> Option<String> {
+ let cx = self.cx.borrow();
+ field.stability_class(cx.tcx())
+ }
+ fn print_ty<'b>(
+ &'b self,
+ ty: &'a clean::Type,
+ ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let cx = self.cx.borrow();
+ let v = ty.print(*cx);
+ write!(f, "{v}")
+ })
+ }
- let mut fields = s
- .fields
- .iter()
- .filter_map(|f| match *f.kind {
- clean::StructFieldItem(ref ty) => Some((f, ty)),
- _ => None,
- })
- .peekable();
- if fields.peek().is_some() {
- write!(
- w,
- "<h2 id=\"fields\" class=\"fields small-section-header\">\
- Fields<a href=\"#fields\" class=\"anchor\">§</a>\
- </h2>"
- );
- for (field, ty) in fields {
- let name = field.name.expect("union field name");
- let id = format!("{}.{}", ItemType::StructField, name);
- write!(
- w,
- "<span id=\"{id}\" class=\"{shortty} small-section-header\">\
- <a href=\"#{id}\" class=\"anchor field\">§</a>\
- <code>{name}: {ty}</code>\
- </span>",
- shortty = ItemType::StructField,
- ty = ty.print(cx),
- );
- if let Some(stability_class) = field.stability_class(cx.tcx()) {
- write!(w, "<span class=\"stab {stability_class}\"></span>");
- }
- document(w, cx, field, Some(it), HeadingOffset::H3);
+ fn fields_iter(
+ &self,
+ ) -> std::iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>> {
+ self.s
+ .fields
+ .iter()
+ .filter_map(|f| match *f.kind {
+ clean::StructFieldItem(ref ty) => Some((f, ty)),
+ _ => None,
+ })
+ .peekable()
}
}
- let def_id = it.item_id.expect_def_id();
- render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
- document_type_layout(w, cx, def_id);
+
+ ItemUnion { cx: std::cell::RefCell::new(cx), it, s }.render_into(w).unwrap();
}
-fn print_tuple_struct_fields(w: &mut Buffer, cx: &Context<'_>, s: &[clean::Item]) {
- for (i, ty) in s.iter().enumerate() {
- if i > 0 {
- w.write_str(", ");
- }
- match *ty.kind {
- clean::StrippedItem(box clean::StructFieldItem(_)) => w.write_str("_"),
- clean::StructFieldItem(ref ty) => write!(w, "{}", ty.print(cx)),
- _ => unreachable!(),
+fn print_tuple_struct_fields<'a, 'cx: 'a>(
+ cx: &'a Context<'cx>,
+ s: &'a [clean::Item],
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ display_fn(|f| {
+ for (i, ty) in s.iter().enumerate() {
+ if i > 0 {
+ f.write_str(", ")?;
+ }
+ match *ty.kind {
+ clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_")?,
+ clean::StructFieldItem(ref ty) => write!(f, "{}", ty.print(cx))?,
+ _ => unreachable!(),
+ }
}
- }
+ Ok(())
+ })
}
fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::Enum) {
let tcx = cx.tcx();
let count_variants = e.variants().count();
- wrap_item(w, |w| {
- render_attributes_in_pre(w, it, "");
+ wrap_item(w, |mut w| {
write!(
w,
- "{}enum {}{}",
+ "{attrs}{}enum {}{}",
visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
it.name.unwrap(),
e.generics.print(cx),
+ attrs = render_attributes_in_pre(it, ""),
);
if !print_where_clause_and_check(w, &e.generics, cx) {
// If there wasn't a `where` clause, we add a whitespace.
@@ -1189,7 +1266,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
w.write_str("{\n");
let toggle = should_hide_fields(count_variants);
if toggle {
- toggle_open(w, format_args!("{} variants", count_variants));
+ toggle_open(&mut w, format_args!("{} variants", count_variants));
}
for v in e.variants() {
w.write_str(" ");
@@ -1199,9 +1276,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
clean::VariantItem(ref var) => match var.kind {
clean::VariantKind::CLike => write!(w, "{}", name),
clean::VariantKind::Tuple(ref s) => {
- write!(w, "{}(", name);
- print_tuple_struct_fields(w, cx, s);
- w.write_str(")");
+ write!(w, "{name}({})", print_tuple_struct_fields(cx, s),);
}
clean::VariantKind::Struct(ref s) => {
render_struct(w, v, None, None, &s.fields, " ", false, cx);
@@ -1212,28 +1287,29 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
w.write_str(",\n");
}
- if variants_stripped {
+ if variants_stripped && !it.is_non_exhaustive() {
w.write_str(" // some variants omitted\n");
}
if toggle {
- toggle_close(w);
+ toggle_close(&mut w);
}
w.write_str("}");
}
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
if count_variants != 0 {
write!(
w,
"<h2 id=\"variants\" class=\"variants small-section-header\">\
Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\
- </h2>",
- document_non_exhaustive_header(it)
+ </h2>\
+ {}\
+ <div class=\"variants\">",
+ document_non_exhaustive_header(it),
+ document_non_exhaustive(it)
);
- document_non_exhaustive(w, it);
- write!(w, "<div class=\"variants\">");
for variant in e.variants() {
let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap()));
write!(
@@ -1254,9 +1330,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
let clean::VariantItem(variant_data) = &*variant.kind else { unreachable!() };
if let clean::VariantKind::Tuple(ref s) = variant_data.kind {
- w.write_str("(");
- print_tuple_struct_fields(w, cx, s);
- w.write_str(")");
+ write!(w, "({})", print_tuple_struct_fields(cx, s),);
}
w.write_str("</h3></section>");
@@ -1280,9 +1354,10 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
write!(
w,
"<div class=\"sub-variant\" id=\"{variant_id}\">\
- <h4>{heading}</h4>",
+ <h4>{heading}</h4>\
+ {}",
+ document_non_exhaustive(variant)
);
- document_non_exhaustive(w, variant);
for field in fields {
match *field.kind {
clean::StrippedItem(box clean::StructFieldItem(_)) => {}
@@ -1300,10 +1375,13 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
<code>{f}: {t}</code>\
</span>",
f = field.name.unwrap(),
- t = ty.print(cx)
+ t = ty.print(cx),
+ );
+ write!(
+ w,
+ "{}</div>",
+ document(cx, field, Some(variant), HeadingOffset::H5)
);
- document(w, cx, field, Some(variant), HeadingOffset::H5);
- write!(w, "</div>");
}
_ => unreachable!(),
}
@@ -1311,18 +1389,18 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
w.write_str("</div>");
}
- document(w, cx, variant, Some(it), HeadingOffset::H4);
+ write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4));
}
write!(w, "</div>");
}
let def_id = it.item_id.expect_def_id();
- render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
- document_type_layout(w, cx, def_id);
+ write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
+ write!(w, "{}", document_type_layout(cx, def_id));
}
fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Macro) {
highlight::render_item_decl_with_highlighting(&t.source, w);
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &clean::ProcMacro) {
@@ -1348,14 +1426,14 @@ fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &c
}
}
});
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
let def_id = it.item_id.expect_def_id();
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
- render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
+ write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
} else {
// We handle the "reference" primitive type on its own because we only want to list
// implementations on generic types.
@@ -1411,7 +1489,7 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle
}
});
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) {
@@ -1420,7 +1498,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
let mut fields = s
.fields
@@ -1436,11 +1514,12 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
w,
"<h2 id=\"fields\" class=\"fields small-section-header\">\
{}{}<a href=\"#fields\" class=\"anchor\">§</a>\
- </h2>",
+ </h2>\
+ {}",
if s.ctor_kind.is_none() { "Fields" } else { "Tuple Fields" },
- document_non_exhaustive_header(it)
+ document_non_exhaustive_header(it),
+ document_non_exhaustive(it)
);
- document_non_exhaustive(w, it);
for (index, (field, ty)) in fields.enumerate() {
let field_name =
field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string());
@@ -1454,13 +1533,13 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
item_type = ItemType::StructField,
ty = ty.print(cx)
);
- document(w, cx, field, Some(it), HeadingOffset::H3);
+ write!(w, "{}", document(cx, field, Some(it), HeadingOffset::H3));
}
}
}
let def_id = it.item_id.expect_def_id();
- render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
- document_type_layout(w, cx, def_id);
+ write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
+ write!(w, "{}", document_type_layout(cx, def_id));
}
fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) {
@@ -1475,7 +1554,7 @@ fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
typ = s.type_.print(cx)
);
});
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
@@ -1490,13 +1569,13 @@ fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
- render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All)
+ write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
}
fn item_keyword(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order).
@@ -1575,12 +1654,25 @@ where
w.write_str("</code></pre>");
}
-fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cx: &Context<'_>) -> Ordering {
- let lhss = format!("{}", lhs.inner_impl().print(false, cx));
- let rhss = format!("{}", rhs.inner_impl().print(false, cx));
+#[derive(PartialEq, Eq)]
+struct ImplString(String);
- // lhs and rhs are formatted as HTML, which may be unnecessary
- compare_names(&lhss, &rhss)
+impl ImplString {
+ fn new(i: &Impl, cx: &Context<'_>) -> ImplString {
+ ImplString(format!("{}", i.inner_impl().print(false, cx)))
+ }
+}
+
+impl PartialOrd for ImplString {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(Ord::cmp(self, other))
+ }
+}
+
+impl Ord for ImplString {
+ fn cmp(&self, other: &Self) -> Ordering {
+ compare_names(&self.0, &other.0)
+ }
}
fn render_implementor(
@@ -1620,64 +1712,69 @@ fn render_implementor(
);
}
-fn render_union(
- w: &mut Buffer,
- it: &clean::Item,
- g: Option<&clean::Generics>,
- fields: &[clean::Item],
- cx: &Context<'_>,
-) {
- let tcx = cx.tcx();
- write!(
- w,
- "{}union {}",
- visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
- it.name.unwrap(),
- );
-
- let where_displayed = g
- .map(|g| {
- write!(w, "{}", g.print(cx));
- print_where_clause_and_check(w, g, cx)
- })
- .unwrap_or(false);
+fn render_union<'a, 'cx: 'a>(
+ it: &'a clean::Item,
+ g: Option<&'a clean::Generics>,
+ fields: &'a [clean::Item],
+ cx: &'a Context<'cx>,
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ display_fn(move |mut f| {
+ let tcx = cx.tcx();
+ write!(
+ f,
+ "{}union {}",
+ visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
+ it.name.unwrap(),
+ )?;
+
+ let where_displayed = g
+ .map(|g| {
+ let mut buf = Buffer::html();
+ write!(buf, "{}", g.print(cx));
+ let where_displayed = print_where_clause_and_check(&mut buf, g, cx);
+ write!(f, "{buf}", buf = buf.into_inner()).unwrap();
+ where_displayed
+ })
+ .unwrap_or(false);
- // If there wasn't a `where` clause, we add a whitespace.
- if !where_displayed {
- w.write_str(" ");
- }
+ // If there wasn't a `where` clause, we add a whitespace.
+ if !where_displayed {
+ f.write_str(" ")?;
+ }
- write!(w, "{{\n");
- let count_fields =
- fields.iter().filter(|f| matches!(*f.kind, clean::StructFieldItem(..))).count();
- let toggle = should_hide_fields(count_fields);
- if toggle {
- toggle_open(w, format_args!("{} fields", count_fields));
- }
+ write!(f, "{{\n")?;
+ let count_fields =
+ fields.iter().filter(|field| matches!(*field.kind, clean::StructFieldItem(..))).count();
+ let toggle = should_hide_fields(count_fields);
+ if toggle {
+ toggle_open(&mut f, format_args!("{} fields", count_fields));
+ }
- for field in fields {
- if let clean::StructFieldItem(ref ty) = *field.kind {
- write!(
- w,
- " {}{}: {},\n",
- visibility_print_with_space(field.visibility(tcx), field.item_id, cx),
- field.name.unwrap(),
- ty.print(cx)
- );
+ for field in fields {
+ if let clean::StructFieldItem(ref ty) = *field.kind {
+ write!(
+ f,
+ " {}{}: {},\n",
+ visibility_print_with_space(field.visibility(tcx), field.item_id, cx),
+ field.name.unwrap(),
+ ty.print(cx)
+ )?;
+ }
}
- }
- if it.has_stripped_entries().unwrap() {
- write!(w, " /* private fields */\n");
- }
- if toggle {
- toggle_close(w);
- }
- w.write_str("}");
+ if it.has_stripped_entries().unwrap() {
+ write!(f, " /* private fields */\n")?;
+ }
+ if toggle {
+ toggle_close(&mut f);
+ }
+ f.write_str("}").unwrap();
+ Ok(())
+ })
}
fn render_struct(
- w: &mut Buffer,
+ mut w: &mut Buffer,
it: &clean::Item,
g: Option<&clean::Generics>,
ty: Option<CtorKind>,
@@ -1699,10 +1796,11 @@ fn render_struct(
}
match ty {
None => {
- let where_diplayed = g.map(|g| print_where_clause_and_check(w, g, cx)).unwrap_or(false);
+ let where_displayed =
+ g.map(|g| print_where_clause_and_check(w, g, cx)).unwrap_or(false);
// If there wasn't a `where` clause, we add a whitespace.
- if !where_diplayed {
+ if !where_displayed {
w.write_str(" {");
} else {
w.write_str("{");
@@ -1712,7 +1810,7 @@ fn render_struct(
let has_visible_fields = count_fields > 0;
let toggle = should_hide_fields(count_fields);
if toggle {
- toggle_open(w, format_args!("{} fields", count_fields));
+ toggle_open(&mut w, format_args!("{} fields", count_fields));
}
for field in fields {
if let clean::StructFieldItem(ref ty) = *field.kind {
@@ -1736,7 +1834,7 @@ fn render_struct(
write!(w, " /* private fields */ ");
}
if toggle {
- toggle_close(w);
+ toggle_close(&mut w);
}
w.write_str("}");
}
@@ -1782,155 +1880,169 @@ fn document_non_exhaustive_header(item: &clean::Item) -> &str {
if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" }
}
-fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) {
- if item.is_non_exhaustive() {
- write!(
- w,
- "<details class=\"toggle non-exhaustive\">\
- <summary class=\"hideme\"><span>{}</span></summary>\
- <div class=\"docblock\">",
- {
- if item.is_struct() {
- "This struct is marked as non-exhaustive"
- } else if item.is_enum() {
- "This enum is marked as non-exhaustive"
- } else if item.is_variant() {
- "This variant is marked as non-exhaustive"
- } else {
- "This type is marked as non-exhaustive"
+fn document_non_exhaustive<'a>(item: &'a clean::Item) -> impl fmt::Display + 'a {
+ display_fn(|f| {
+ if item.is_non_exhaustive() {
+ write!(
+ f,
+ "<details class=\"toggle non-exhaustive\">\
+ <summary class=\"hideme\"><span>{}</span></summary>\
+ <div class=\"docblock\">",
+ {
+ if item.is_struct() {
+ "This struct is marked as non-exhaustive"
+ } else if item.is_enum() {
+ "This enum is marked as non-exhaustive"
+ } else if item.is_variant() {
+ "This variant is marked as non-exhaustive"
+ } else {
+ "This type is marked as non-exhaustive"
+ }
}
+ )?;
+
+ if item.is_struct() {
+ f.write_str(
+ "Non-exhaustive structs could have additional fields added in future. \
+ Therefore, non-exhaustive structs cannot be constructed in external crates \
+ using the traditional <code>Struct { .. }</code> syntax; cannot be \
+ matched against without a wildcard <code>..</code>; and \
+ struct update syntax will not work.",
+ )?;
+ } else if item.is_enum() {
+ f.write_str(
+ "Non-exhaustive enums could have additional variants added in future. \
+ Therefore, when matching against variants of non-exhaustive enums, an \
+ extra wildcard arm must be added to account for any future variants.",
+ )?;
+ } else if item.is_variant() {
+ f.write_str(
+ "Non-exhaustive enum variants could have additional fields added in future. \
+ Therefore, non-exhaustive enum variants cannot be constructed in external \
+ crates and cannot be matched against.",
+ )?;
+ } else {
+ f.write_str(
+ "This type will require a wildcard arm in any match statements or constructors.",
+ )?;
}
- );
- if item.is_struct() {
- w.write_str(
- "Non-exhaustive structs could have additional fields added in future. \
- Therefore, non-exhaustive structs cannot be constructed in external crates \
- using the traditional <code>Struct { .. }</code> syntax; cannot be \
- matched against without a wildcard <code>..</code>; and \
- struct update syntax will not work.",
- );
- } else if item.is_enum() {
- w.write_str(
- "Non-exhaustive enums could have additional variants added in future. \
- Therefore, when matching against variants of non-exhaustive enums, an \
- extra wildcard arm must be added to account for any future variants.",
- );
- } else if item.is_variant() {
- w.write_str(
- "Non-exhaustive enum variants could have additional fields added in future. \
- Therefore, non-exhaustive enum variants cannot be constructed in external \
- crates and cannot be matched against.",
- );
- } else {
- w.write_str(
- "This type will require a wildcard arm in any match statements or constructors.",
- );
+ f.write_str("</div></details>")?;
}
-
- w.write_str("</div></details>");
- }
+ Ok(())
+ })
}
-fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) {
- fn write_size_of_layout(w: &mut Buffer, layout: &LayoutS, tag_size: u64) {
+fn document_type_layout<'a, 'cx: 'a>(
+ cx: &'a Context<'cx>,
+ ty_def_id: DefId,
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ fn write_size_of_layout(mut w: impl fmt::Write, layout: &LayoutS, tag_size: u64) {
if layout.abi.is_unsized() {
- write!(w, "(unsized)");
+ write!(w, "(unsized)").unwrap();
} else {
let size = layout.size.bytes() - tag_size;
- write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" },);
+ write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" }).unwrap();
+ if layout.abi.is_uninhabited() {
+ write!(
+ w,
+ " (<a href=\"https://doc.rust-lang.org/stable/reference/glossary.html#uninhabited\">uninhabited</a>)"
+ ).unwrap();
+ }
}
}
- if !cx.shared.show_type_layout {
- return;
- }
-
- writeln!(
- w,
- "<h2 id=\"layout\" class=\"small-section-header\"> \
- Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>"
- );
- writeln!(w, "<div class=\"docblock\">");
-
- let tcx = cx.tcx();
- let param_env = tcx.param_env(ty_def_id);
- let ty = tcx.type_of(ty_def_id).subst_identity();
- match tcx.layout_of(param_env.and(ty)) {
- Ok(ty_layout) => {
- writeln!(
- w,
- "<div class=\"warning\"><p><strong>Note:</strong> Most layout information is \
- <strong>completely unstable</strong> and may even differ between compilations. \
- The only exception is types with certain <code>repr(...)</code> attributes. \
- Please see the Rust Reference’s \
- <a href=\"https://doc.rust-lang.org/reference/type-layout.html\">“Type Layout”</a> \
- chapter for details on type layout guarantees.</p></div>"
- );
- w.write_str("<p><strong>Size:</strong> ");
- write_size_of_layout(w, &ty_layout.layout.0, 0);
- writeln!(w, "</p>");
- if let Variants::Multiple { variants, tag, tag_encoding, .. } =
- &ty_layout.layout.variants()
- {
- if !variants.is_empty() {
- w.write_str(
- "<p><strong>Size for each variant:</strong></p>\
- <ul>",
- );
-
- let Adt(adt, _) = ty_layout.ty.kind() else {
- span_bug!(tcx.def_span(ty_def_id), "not an adt")
- };
+ display_fn(move |mut f| {
+ if !cx.shared.show_type_layout {
+ return Ok(());
+ }
- 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")
- };
+ writeln!(
+ f,
+ "<h2 id=\"layout\" class=\"small-section-header\"> \
+ Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>"
+ )?;
+ writeln!(f, "<div class=\"docblock\">")?;
- for (index, layout) in variants.iter_enumerated() {
- let name = adt.variant(index).name;
- write!(w, "<li><code>{name}</code>: ");
- write_size_of_layout(w, layout, tag_size);
- writeln!(w, "</li>");
+ let tcx = cx.tcx();
+ let param_env = tcx.param_env(ty_def_id);
+ let ty = tcx.type_of(ty_def_id).subst_identity();
+ match tcx.layout_of(param_env.and(ty)) {
+ Ok(ty_layout) => {
+ writeln!(
+ f,
+ "<div class=\"warning\"><p><strong>Note:</strong> Most layout information is \
+ <strong>completely unstable</strong> and may even differ between compilations. \
+ The only exception is types with certain <code>repr(...)</code> attributes. \
+ Please see the Rust Reference’s \
+ <a href=\"https://doc.rust-lang.org/reference/type-layout.html\">“Type Layout”</a> \
+ chapter for details on type layout guarantees.</p></div>"
+ )?;
+ f.write_str("<p><strong>Size:</strong> ")?;
+ write_size_of_layout(&mut f, &ty_layout.layout.0, 0);
+ writeln!(f, "</p>")?;
+ if let Variants::Multiple { variants, tag, tag_encoding, .. } =
+ &ty_layout.layout.variants()
+ {
+ if !variants.is_empty() {
+ f.write_str(
+ "<p><strong>Size for each variant:</strong></p>\
+ <ul>",
+ )?;
+
+ let Adt(adt, _) = ty_layout.ty.kind() else {
+ span_bug!(tcx.def_span(ty_def_id), "not an adt")
+ };
+
+ let tag_size = if let TagEncoding::Niche { .. } = tag_encoding {
+ 0
+ } else if let Primitive::Int(i, _) = tag.primitive() {
+ i.size().bytes()
+ } else {
+ span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int")
+ };
+
+ for (index, layout) in variants.iter_enumerated() {
+ let name = adt.variant(index).name;
+ write!(&mut f, "<li><code>{name}</code>: ")?;
+ write_size_of_layout(&mut f, layout, tag_size);
+ writeln!(&mut f, "</li>")?;
+ }
+ f.write_str("</ul>")?;
}
- w.write_str("</ul>");
}
}
+ // This kind of layout error can occur with valid code, e.g. if you try to
+ // get the layout of a generic type such as `Vec<T>`.
+ Err(LayoutError::Unknown(_)) => {
+ writeln!(
+ f,
+ "<p><strong>Note:</strong> Unable to compute type layout, \
+ possibly due to this type having generic parameters. \
+ Layout can only be computed for concrete, fully-instantiated types.</p>"
+ )?;
+ }
+ // This kind of error probably can't happen with valid code, but we don't
+ // want to panic and prevent the docs from building, so we just let the
+ // user know that we couldn't compute the layout.
+ Err(LayoutError::SizeOverflow(_)) => {
+ writeln!(
+ f,
+ "<p><strong>Note:</strong> Encountered an error during type layout; \
+ the type was too big.</p>"
+ )?;
+ }
+ Err(LayoutError::NormalizationFailure(_, _)) => {
+ writeln!(
+ f,
+ "<p><strong>Note:</strong> Encountered an error during type layout; \
+ the type failed to be normalized.</p>"
+ )?;
+ }
}
- // This kind of layout error can occur with valid code, e.g. if you try to
- // get the layout of a generic type such as `Vec<T>`.
- Err(LayoutError::Unknown(_)) => {
- writeln!(
- w,
- "<p><strong>Note:</strong> Unable to compute type layout, \
- possibly due to this type having generic parameters. \
- Layout can only be computed for concrete, fully-instantiated types.</p>"
- );
- }
- // This kind of error probably can't happen with valid code, but we don't
- // want to panic and prevent the docs from building, so we just let the
- // user know that we couldn't compute the layout.
- Err(LayoutError::SizeOverflow(_)) => {
- writeln!(
- w,
- "<p><strong>Note:</strong> Encountered an error during type layout; \
- the type was too big.</p>"
- );
- }
- Err(LayoutError::NormalizationFailure(_, _)) => {
- writeln!(
- w,
- "<p><strong>Note:</strong> Encountered an error during type layout; \
- the type failed to be normalized.</p>"
- )
- }
- }
- writeln!(w, "</div>");
+ writeln!(f, "</div>")
+ })
}
fn pluralize(count: usize) -> &'static str {
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index 090ea2cb1..f5b4a3f5a 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -7,9 +7,7 @@ use rustc_span::symbol::Symbol;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use crate::clean;
-use crate::clean::types::{
- FnRetTy, Function, GenericBound, Generics, ItemId, Type, WherePredicate,
-};
+use crate::clean::types::{FnRetTy, Function, Generics, ItemId, Type, WherePredicate};
use crate::formats::cache::{Cache, OrphanImplItem};
use crate::formats::item_type::ItemType;
use crate::html::format::join_with_double_colon;
@@ -42,6 +40,7 @@ pub(crate) fn build_index<'tcx>(
parent_idx: None,
search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache),
aliases: item.attrs.get_doc_aliases(),
+ deprecation: item.deprecation(tcx),
});
}
}
@@ -60,7 +59,7 @@ pub(crate) fn build_index<'tcx>(
// `sort_unstable_by_key` produces lifetime errors
let k1 = (&k1.path, k1.name.as_str(), &k1.ty, &k1.parent);
let k2 = (&k2.path, k2.name.as_str(), &k2.ty, &k2.parent);
- std::cmp::Ord::cmp(&k1, &k2)
+ Ord::cmp(&k1, &k2)
});
// Set up alias indexes.
@@ -253,7 +252,17 @@ pub(crate) fn build_index<'tcx>(
)?;
crate_data.serialize_field(
"q",
- &self.items.iter().map(|item| &item.path).collect::<Vec<_>>(),
+ &self
+ .items
+ .iter()
+ .enumerate()
+ // Serialize as an array of item indices and full paths
+ .filter_map(
+ |(index, item)| {
+ if item.path.is_empty() { None } else { Some((index, &item.path)) }
+ },
+ )
+ .collect::<Vec<_>>(),
)?;
crate_data.serialize_field(
"d",
@@ -307,6 +316,16 @@ pub(crate) fn build_index<'tcx>(
.collect::<Vec<_>>(),
)?;
crate_data.serialize_field(
+ "c",
+ &self
+ .items
+ .iter()
+ .enumerate()
+ // Serialize as an array of deprecated item indices
+ .filter_map(|(index, item)| item.deprecation.map(|_| index))
+ .collect::<Vec<_>>(),
+ )?;
+ crate_data.serialize_field(
"p",
&self.paths.iter().map(|(it, s)| (it, s.as_str())).collect::<Vec<_>>(),
)?;
@@ -467,7 +486,7 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>(
}
// First, check if it's "Self".
- let arg = if let Some(self_) = self_ {
+ 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_,
@@ -477,34 +496,33 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>(
arg
};
+ // strip references from the argument type
+ while let Type::BorrowedRef { type_, .. } = &*arg {
+ arg = &*type_;
+ }
+
// If this argument is a type parameter and not a trait bound or a type, we need to look
// for its bounds.
if let Type::Generic(arg_s) = *arg {
// First we check if the bounds are in a `where` predicate...
- if let Some(where_pred) = generics.where_predicates.iter().find(|g| match g {
- WherePredicate::BoundPredicate { ty, .. } => ty.def_id(cache) == arg.def_id(cache),
+ for where_pred in generics.where_predicates.iter().filter(|g| match g {
+ WherePredicate::BoundPredicate { ty: Type::Generic(ty_s), .. } => *ty_s == arg_s,
_ => false,
}) {
let mut ty_generics = Vec::new();
let bounds = where_pred.get_bounds().unwrap_or_else(|| &[]);
for bound in bounds.iter() {
- if let GenericBound::TraitBound(poly_trait, _) = bound {
- for param_def in poly_trait.generic_params.iter() {
- match &param_def.kind {
- clean::GenericParamDefKind::Type { default: Some(ty), .. } => {
- add_generics_and_bounds_as_types(
- self_,
- generics,
- ty,
- tcx,
- recurse + 1,
- &mut ty_generics,
- cache,
- )
- }
- _ => {}
- }
- }
+ if let Some(path) = bound.get_trait_path() {
+ let ty = Type::Path { path };
+ add_generics_and_bounds_as_types(
+ self_,
+ generics,
+ &ty,
+ tcx,
+ recurse + 1,
+ &mut ty_generics,
+ cache,
+ );
}
}
insert_ty(res, arg.clone(), ty_generics);
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
new file mode 100644
index 000000000..455b4e9ae
--- /dev/null
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -0,0 +1,558 @@
+use std::{borrow::Cow, rc::Rc};
+
+use askama::Template;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_hir::{def::CtorKind, def_id::DefIdSet};
+use rustc_middle::ty::{self, TyCtxt};
+
+use crate::{
+ clean,
+ formats::{item_type::ItemType, Impl},
+ html::{format::Buffer, markdown::IdMap},
+};
+
+use super::{item_ty_to_section, Context, ItemSection};
+
+#[derive(Template)]
+#[template(path = "sidebar.html")]
+pub(super) struct Sidebar<'a> {
+ pub(super) title_prefix: &'static str,
+ pub(super) title: &'a str,
+ pub(super) is_crate: bool,
+ pub(super) version: &'a str,
+ pub(super) blocks: Vec<LinkBlock<'a>>,
+ pub(super) path: String,
+}
+
+impl<'a> Sidebar<'a> {
+ /// Only create a `<section>` if there are any blocks
+ /// which should actually be rendered.
+ pub fn should_render_blocks(&self) -> bool {
+ self.blocks.iter().any(LinkBlock::should_render)
+ }
+}
+
+/// A sidebar section such as 'Methods'.
+pub(crate) struct LinkBlock<'a> {
+ /// The name of this section, e.g. 'Methods'
+ /// as well as the link to it, e.g. `#implementations`.
+ /// Will be rendered inside an `<h3>` tag
+ heading: Link<'a>,
+ links: Vec<Link<'a>>,
+ /// Render the heading even if there are no links
+ force_render: bool,
+}
+
+impl<'a> LinkBlock<'a> {
+ pub fn new(heading: Link<'a>, links: Vec<Link<'a>>) -> Self {
+ Self { heading, links, force_render: false }
+ }
+
+ pub fn forced(heading: Link<'a>) -> Self {
+ Self { heading, links: vec![], force_render: true }
+ }
+
+ pub fn should_render(&self) -> bool {
+ self.force_render || !self.links.is_empty()
+ }
+}
+
+/// A link to an item. Content should not be escaped.
+#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone)]
+pub(crate) struct Link<'a> {
+ /// The content for the anchor tag
+ name: Cow<'a, str>,
+ /// The id of an anchor within the page (without a `#` prefix)
+ href: Cow<'a, str>,
+}
+
+impl<'a> Link<'a> {
+ pub fn new(href: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>) -> Self {
+ Self { href: href.into(), name: name.into() }
+ }
+ pub fn empty() -> Link<'static> {
+ Link::new("", "")
+ }
+}
+
+pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
+ let blocks: Vec<LinkBlock<'_>> = match *it.kind {
+ clean::StructItem(ref s) => sidebar_struct(cx, it, s),
+ clean::TraitItem(ref t) => sidebar_trait(cx, it, t),
+ clean::PrimitiveItem(_) => sidebar_primitive(cx, it),
+ clean::UnionItem(ref u) => sidebar_union(cx, it, u),
+ clean::EnumItem(ref e) => sidebar_enum(cx, it, e),
+ clean::TypedefItem(_) => sidebar_typedef(cx, it),
+ clean::ModuleItem(ref m) => vec![sidebar_module(&m.items)],
+ clean::ForeignTypeItem => sidebar_foreign_type(cx, it),
+ _ => vec![],
+ };
+ // The sidebar is designed to display sibling functions, modules and
+ // other miscellaneous information. since there are lots of sibling
+ // items (and that causes quadratic growth in large modules),
+ // we refactor common parts into a shared JavaScript file per module.
+ // still, we don't move everything into JS because we want to preserve
+ // as much HTML as possible in order to allow non-JS-enabled browsers
+ // to navigate the documentation (though slightly inefficiently).
+ let (title_prefix, title) = if it.is_struct()
+ || it.is_trait()
+ || it.is_primitive()
+ || it.is_union()
+ || it.is_enum()
+ || it.is_mod()
+ || it.is_typedef()
+ {
+ (
+ match *it.kind {
+ clean::ModuleItem(..) if it.is_crate() => "Crate ",
+ clean::ModuleItem(..) => "Module ",
+ _ => "",
+ },
+ it.name.as_ref().unwrap().as_str(),
+ )
+ } else {
+ ("", "")
+ };
+ let version =
+ if it.is_crate() { cx.cache().crate_version.as_deref().unwrap_or_default() } else { "" };
+ let path: String = if !it.is_mod() {
+ cx.current.iter().map(|s| s.as_str()).intersperse("::").collect()
+ } else {
+ "".into()
+ };
+ let sidebar = Sidebar { title_prefix, title, is_crate: it.is_crate(), version, blocks, path };
+ sidebar.render_into(buffer).unwrap();
+}
+
+fn get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec<Link<'a>> {
+ let mut fields = fields
+ .iter()
+ .filter(|f| matches!(*f.kind, clean::StructFieldItem(..)))
+ .filter_map(|f| {
+ f.name.as_ref().map(|name| Link::new(format!("structfield.{name}"), name.as_str()))
+ })
+ .collect::<Vec<Link<'a>>>();
+ fields.sort();
+ fields
+}
+
+fn sidebar_struct<'a>(
+ cx: &'a Context<'_>,
+ it: &'a clean::Item,
+ s: &'a clean::Struct,
+) -> Vec<LinkBlock<'a>> {
+ let fields = get_struct_fields_name(&s.fields);
+ let field_name = match s.ctor_kind {
+ Some(CtorKind::Fn) => Some("Tuple Fields"),
+ None => Some("Fields"),
+ _ => None,
+ };
+ let mut items = vec![];
+ if let Some(name) = field_name {
+ items.push(LinkBlock::new(Link::new("fields", name), fields));
+ }
+ sidebar_assoc_items(cx, it, &mut items);
+ items
+}
+
+fn sidebar_trait<'a>(
+ cx: &'a Context<'_>,
+ it: &'a clean::Item,
+ t: &'a clean::Trait,
+) -> Vec<LinkBlock<'a>> {
+ fn filter_items<'a>(
+ items: &'a [clean::Item],
+ filt: impl Fn(&clean::Item) -> bool,
+ ty: &str,
+ ) -> Vec<Link<'a>> {
+ let mut res = items
+ .iter()
+ .filter_map(|m: &clean::Item| match m.name {
+ Some(ref name) if filt(m) => Some(Link::new(format!("{ty}.{name}"), name.as_str())),
+ _ => None,
+ })
+ .collect::<Vec<Link<'a>>>();
+ res.sort();
+ res
+ }
+
+ let req_assoc = filter_items(&t.items, |m| m.is_ty_associated_type(), "associatedtype");
+ let prov_assoc = filter_items(&t.items, |m| m.is_associated_type(), "associatedtype");
+ let req_assoc_const =
+ filter_items(&t.items, |m| m.is_ty_associated_const(), "associatedconstant");
+ let prov_assoc_const =
+ filter_items(&t.items, |m| m.is_associated_const(), "associatedconstant");
+ let req_method = filter_items(&t.items, |m| m.is_ty_method(), "tymethod");
+ let prov_method = filter_items(&t.items, |m| m.is_method(), "method");
+ let mut foreign_impls = vec![];
+ if let Some(implementors) = cx.cache().implementors.get(&it.item_id.expect_def_id()) {
+ foreign_impls.extend(
+ implementors
+ .iter()
+ .filter(|i| !i.is_on_local_type(cx))
+ .filter_map(|i| super::extract_for_impl_name(&i.impl_item, cx))
+ .map(|(name, id)| Link::new(id, name)),
+ );
+ foreign_impls.sort();
+ }
+
+ let mut blocks: Vec<LinkBlock<'_>> = [
+ ("required-associated-types", "Required Associated Types", req_assoc),
+ ("provided-associated-types", "Provided Associated Types", prov_assoc),
+ ("required-associated-consts", "Required Associated Constants", req_assoc_const),
+ ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const),
+ ("required-methods", "Required Methods", req_method),
+ ("provided-methods", "Provided Methods", prov_method),
+ ("foreign-impls", "Implementations on Foreign Types", foreign_impls),
+ ]
+ .into_iter()
+ .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), items))
+ .collect();
+ sidebar_assoc_items(cx, it, &mut blocks);
+ blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors")));
+ if t.is_auto(cx.tcx()) {
+ blocks.push(LinkBlock::forced(Link::new("synthetic-implementors", "Auto Implementors")));
+ }
+ blocks
+}
+
+fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
+ if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
+ let mut items = vec![];
+ sidebar_assoc_items(cx, it, &mut items);
+ items
+ } else {
+ let shared = Rc::clone(&cx.shared);
+ let (concrete, synthetic, blanket_impl) =
+ super::get_filtered_impls_for_reference(&shared, it);
+
+ sidebar_render_assoc_items(cx, &mut IdMap::new(), concrete, synthetic, blanket_impl).into()
+ }
+}
+
+fn sidebar_typedef<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
+ let mut items = vec![];
+ sidebar_assoc_items(cx, it, &mut items);
+ items
+}
+
+fn sidebar_union<'a>(
+ cx: &'a Context<'_>,
+ it: &'a clean::Item,
+ u: &'a clean::Union,
+) -> Vec<LinkBlock<'a>> {
+ let fields = get_struct_fields_name(&u.fields);
+ let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), fields)];
+ sidebar_assoc_items(cx, it, &mut items);
+ items
+}
+
+/// Adds trait implementations into the blocks of links
+fn sidebar_assoc_items<'a>(
+ cx: &'a Context<'_>,
+ it: &'a clean::Item,
+ links: &mut Vec<LinkBlock<'a>>,
+) {
+ let did = it.item_id.expect_def_id();
+ let cache = cx.cache();
+
+ let mut assoc_consts = Vec::new();
+ let mut methods = Vec::new();
+ if let Some(v) = cache.impls.get(&did) {
+ let mut used_links = FxHashSet::default();
+ let mut id_map = IdMap::new();
+
+ {
+ let used_links_bor = &mut used_links;
+ assoc_consts.extend(
+ v.iter()
+ .filter(|i| i.inner_impl().trait_.is_none())
+ .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)),
+ );
+ // We want links' order to be reproducible so we don't use unstable sort.
+ assoc_consts.sort();
+
+ #[rustfmt::skip] // rustfmt makes the pipeline less readable
+ methods.extend(
+ v.iter()
+ .filter(|i| i.inner_impl().trait_.is_none())
+ .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())),
+ );
+
+ // We want links' order to be reproducible so we don't use unstable sort.
+ methods.sort();
+ }
+
+ let mut deref_methods = Vec::new();
+ let [concrete, synthetic, blanket] = if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
+ if let Some(impl_) =
+ v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait())
+ {
+ let mut derefs = DefIdSet::default();
+ derefs.insert(did);
+ sidebar_deref_methods(
+ cx,
+ &mut deref_methods,
+ impl_,
+ v,
+ &mut derefs,
+ &mut used_links,
+ );
+ }
+
+ let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
+ v.iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_auto());
+ let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) =
+ concrete.into_iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_blanket());
+
+ sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl)
+ } else {
+ std::array::from_fn(|_| LinkBlock::new(Link::empty(), vec![]))
+ };
+
+ let mut blocks = vec![
+ LinkBlock::new(Link::new("implementations", "Associated Constants"), assoc_consts),
+ LinkBlock::new(Link::new("implementations", "Methods"), methods),
+ ];
+ blocks.append(&mut deref_methods);
+ blocks.extend([concrete, synthetic, blanket]);
+ links.append(&mut blocks);
+ }
+}
+
+fn sidebar_deref_methods<'a>(
+ cx: &'a Context<'_>,
+ out: &mut Vec<LinkBlock<'a>>,
+ impl_: &Impl,
+ v: &[Impl],
+ derefs: &mut DefIdSet,
+ used_links: &mut FxHashSet<String>,
+) {
+ let c = cx.cache();
+
+ debug!("found Deref: {:?}", impl_);
+ if let Some((target, real_target)) =
+ impl_.inner_impl().items.iter().find_map(|item| match *item.kind {
+ clean::AssocTypeItem(box ref t, _) => Some(match *t {
+ clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_),
+ _ => (&t.type_, &t.type_),
+ }),
+ _ => None,
+ })
+ {
+ debug!("found target, real_target: {:?} {:?}", target, real_target);
+ if let Some(did) = target.def_id(c) &&
+ let Some(type_did) = impl_.inner_impl().for_.def_id(c) &&
+ // `impl Deref<Target = S> for S`
+ (did == type_did || !derefs.insert(did))
+ {
+ // Avoid infinite cycles
+ return;
+ }
+ let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait());
+ let inner_impl = target
+ .def_id(c)
+ .or_else(|| {
+ target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned())
+ })
+ .and_then(|did| c.impls.get(&did));
+ if let Some(impls) = inner_impl {
+ debug!("found inner_impl: {:?}", impls);
+ let mut ret = impls
+ .iter()
+ .filter(|i| i.inner_impl().trait_.is_none())
+ .flat_map(|i| get_methods(i.inner_impl(), true, used_links, deref_mut, cx.tcx()))
+ .collect::<Vec<_>>();
+ if !ret.is_empty() {
+ let id = if let Some(target_def_id) = real_target.def_id(c) {
+ Cow::Borrowed(
+ cx.deref_id_map
+ .get(&target_def_id)
+ .expect("Deref section without derived id")
+ .as_str(),
+ )
+ } else {
+ Cow::Borrowed("deref-methods")
+ };
+ let title = format!(
+ "Methods from {:#}<Target={:#}>",
+ impl_.inner_impl().trait_.as_ref().unwrap().print(cx),
+ real_target.print(cx),
+ );
+ // We want links' order to be reproducible so we don't use unstable sort.
+ ret.sort();
+ out.push(LinkBlock::new(Link::new(id, title), ret));
+ }
+ }
+
+ // 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| {
+ i.inner_impl()
+ .trait_
+ .as_ref()
+ .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait())
+ .unwrap_or(false)
+ })
+ {
+ sidebar_deref_methods(
+ cx,
+ out,
+ target_deref_impl,
+ target_impls,
+ derefs,
+ used_links,
+ );
+ }
+ }
+}
+
+fn sidebar_enum<'a>(
+ cx: &'a Context<'_>,
+ it: &'a clean::Item,
+ e: &'a clean::Enum,
+) -> Vec<LinkBlock<'a>> {
+ let mut variants = e
+ .variants()
+ .filter_map(|v| v.name)
+ .map(|name| Link::new(format!("variant.{name}"), name.to_string()))
+ .collect::<Vec<_>>();
+ variants.sort_unstable();
+
+ let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), variants)];
+ sidebar_assoc_items(cx, it, &mut items);
+ items
+}
+
+pub(crate) fn sidebar_module_like(
+ item_sections_in_use: FxHashSet<ItemSection>,
+) -> LinkBlock<'static> {
+ let item_sections = ItemSection::ALL
+ .iter()
+ .copied()
+ .filter(|sec| item_sections_in_use.contains(sec))
+ .map(|sec| Link::new(sec.id(), sec.name()))
+ .collect();
+ LinkBlock::new(Link::empty(), item_sections)
+}
+
+fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> {
+ let item_sections_in_use: FxHashSet<_> = items
+ .iter()
+ .filter(|it| {
+ !it.is_stripped()
+ && it
+ .name
+ .or_else(|| {
+ if let clean::ImportItem(ref i) = *it.kind &&
+ let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None }
+ })
+ .is_some()
+ })
+ .map(|it| item_ty_to_section(it.type_()))
+ .collect();
+
+ sidebar_module_like(item_sections_in_use)
+}
+
+fn sidebar_foreign_type<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
+ let mut items = vec![];
+ sidebar_assoc_items(cx, it, &mut items);
+ items
+}
+
+/// Renders the trait implementations for this type
+fn sidebar_render_assoc_items(
+ cx: &Context<'_>,
+ id_map: &mut IdMap,
+ concrete: Vec<&Impl>,
+ synthetic: Vec<&Impl>,
+ blanket_impl: Vec<&Impl>,
+) -> [LinkBlock<'static>; 3] {
+ let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| {
+ let mut links = FxHashSet::default();
+
+ let mut ret = impls
+ .iter()
+ .filter_map(|it| {
+ let trait_ = it.inner_impl().trait_.as_ref()?;
+ let encoded =
+ id_map.derive(super::get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx));
+
+ let prefix = match it.inner_impl().polarity {
+ ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "",
+ ty::ImplPolarity::Negative => "!",
+ };
+ let generated = Link::new(encoded, format!("{prefix}{:#}", trait_.print(cx)));
+ if links.insert(generated.clone()) { Some(generated) } else { None }
+ })
+ .collect::<Vec<Link<'static>>>();
+ ret.sort();
+ ret
+ };
+
+ let concrete = format_impls(concrete, id_map);
+ let synthetic = format_impls(synthetic, id_map);
+ let blanket = format_impls(blanket_impl, id_map);
+ [
+ LinkBlock::new(Link::new("trait-implementations", "Trait Implementations"), concrete),
+ LinkBlock::new(
+ Link::new("synthetic-implementations", "Auto Trait Implementations"),
+ synthetic,
+ ),
+ LinkBlock::new(Link::new("blanket-implementations", "Blanket Implementations"), blanket),
+ ]
+}
+
+fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String {
+ if used_links.insert(url.clone()) {
+ return url;
+ }
+ let mut add = 1;
+ while !used_links.insert(format!("{}-{}", url, add)) {
+ add += 1;
+ }
+ format!("{}-{}", url, add)
+}
+
+fn get_methods<'a>(
+ i: &'a clean::Impl,
+ for_deref: bool,
+ used_links: &mut FxHashSet<String>,
+ deref_mut: bool,
+ tcx: TyCtxt<'_>,
+) -> Vec<Link<'a>> {
+ i.items
+ .iter()
+ .filter_map(|item| match item.name {
+ Some(ref name) if !name.is_empty() && item.is_method() => {
+ if !for_deref || super::should_render_item(item, deref_mut, tcx) {
+ Some(Link::new(
+ get_next_url(used_links, format!("{}.{}", ItemType::Method, name)),
+ name.as_str(),
+ ))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ })
+ .collect::<Vec<_>>()
+}
+
+fn get_associated_constants<'a>(
+ i: &'a clean::Impl,
+ used_links: &mut FxHashSet<String>,
+) -> Vec<Link<'a>> {
+ i.items
+ .iter()
+ .filter_map(|item| match item.name {
+ Some(ref name) if !name.is_empty() && item.is_associated_const() => Some(Link::new(
+ get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)),
+ name.as_str(),
+ )),
+ _ => None,
+ })
+ .collect::<Vec<_>>()
+}
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
index 4514894ca..eb9262f47 100644
--- a/src/librustdoc/html/render/span_map.rs
+++ b/src/librustdoc/html/render/span_map.rs
@@ -29,12 +29,12 @@ pub(crate) enum LinkFromSrc {
/// This function will do at most two things:
///
-/// 1. Generate a `span` correspondance map which links an item `span` to its definition `span`.
+/// 1. Generate a `span` correspondence map which links an item `span` to its definition `span`.
/// 2. Collect the source code files.
///
-/// It returns the `krate`, the source code files and the `span` correspondance map.
+/// It returns the `krate`, the source code files and the `span` correspondence map.
///
-/// Note about the `span` correspondance map: the keys are actually `(lo, hi)` of `span`s. We don't
+/// Note about the `span` correspondence map: the keys are actually `(lo, hi)` of `span`s. We don't
/// need the `span` context later on, only their position, so instead of keep a whole `Span`, we
/// only keep the `lo` and `hi`.
pub(crate) fn collect_spans_and_sources(
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index 2c90bf4fa..c8397967c 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -1,12 +1,13 @@
use crate::clean;
use crate::docfs::PathError;
use crate::error::Error;
-use crate::html::format::Buffer;
+use crate::html::format;
use crate::html::highlight;
use crate::html::layout;
use crate::html::render::Context;
use crate::visit::DocVisitor;
+use askama::Template;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_middle::ty::TyCtxt;
@@ -15,7 +16,9 @@ use rustc_span::source_map::FileName;
use std::cell::RefCell;
use std::ffi::OsStr;
+use std::fmt;
use std::fs;
+use std::ops::RangeInclusive;
use std::path::{Component, Path, PathBuf};
use std::rc::Rc;
@@ -85,7 +88,7 @@ impl LocalSourcesCollector<'_, '_> {
},
);
- let mut href = href.into_inner().to_string_lossy().to_string();
+ let mut href = href.into_inner().to_string_lossy().into_owned();
if let Some(c) = href.as_bytes().last() && *c != b'/' {
href.push('/');
}
@@ -291,7 +294,7 @@ pub(crate) enum SourceContext {
/// Wrapper struct to render the source code of a file. This will do things like
/// adding line numbers to the left-hand side.
pub(crate) fn print_src(
- buf: &mut Buffer,
+ mut writer: impl fmt::Write,
s: &str,
file_span: rustc_span::Span,
context: &Context<'_>,
@@ -299,39 +302,32 @@ pub(crate) fn print_src(
decoration_info: highlight::DecorationInfo,
source_context: SourceContext,
) {
+ #[derive(Template)]
+ #[template(path = "source.html")]
+ struct Source<Code: std::fmt::Display> {
+ embedded: bool,
+ needs_expansion: bool,
+ lines: RangeInclusive<usize>,
+ code_html: Code,
+ }
let lines = s.lines().count();
- let mut line_numbers = Buffer::empty_from(buf);
- let extra;
- line_numbers.write_str("<pre class=\"src-line-numbers\">");
+ let (embedded, needs_expansion, lines) = match source_context {
+ SourceContext::Standalone => (false, false, 1..=lines),
+ SourceContext::Embedded { offset, needs_expansion } => {
+ (true, needs_expansion, (1 + offset)..=(lines + offset))
+ }
+ };
let current_href = context
.href_from_span(clean::Span::new(file_span), false)
.expect("only local crates should have sources emitted");
- match source_context {
- SourceContext::Standalone => {
- extra = None;
- for line in 1..=lines {
- writeln!(line_numbers, "<a href=\"#{line}\" id=\"{line}\">{line}</a>")
- }
- }
- SourceContext::Embedded { offset, needs_expansion } => {
- extra = if needs_expansion {
- Some(r#"<button class="expand">&varr;</button>"#)
- } else {
- None
- };
- for line_number in 1..=lines {
- let line = line_number + offset;
- writeln!(line_numbers, "<span>{line}</span>")
- }
- }
- }
- line_numbers.write_str("</pre>");
- highlight::render_source_with_highlighting(
- s,
- buf,
- line_numbers,
- highlight::HrefContext { context, file_span, root_path, current_href },
- decoration_info,
- extra,
- );
+ let code = format::display_fn(move |fmt| {
+ highlight::write_code(
+ fmt,
+ s,
+ Some(highlight::HrefContext { context, file_span, root_path, current_href }),
+ Some(decoration_info),
+ );
+ Ok(())
+ });
+ Source { embedded, needs_expansion, lines, code_html: code }.render_into(&mut writer).unwrap();
}
diff --git a/src/librustdoc/html/static/COPYRIGHT.txt b/src/librustdoc/html/static/COPYRIGHT.txt
index 34e48134c..1447df792 100644
--- a/src/librustdoc/html/static/COPYRIGHT.txt
+++ b/src/librustdoc/html/static/COPYRIGHT.txt
@@ -1,3 +1,5 @@
+# REUSE-IgnoreStart
+
These documentation pages include resources by third parties. This copyright
file applies only to those resources. The following third party resources are
included, and carry their own copyright notices and license terms:
@@ -44,3 +46,5 @@ included, and carry their own copyright notices and license terms:
See SourceSerif4-LICENSE.md.
This copyright file is intended to be distributed with rustdoc output.
+
+# REUSE-IgnoreEnd
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 95528e70e..6fbb45086 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -6,6 +6,10 @@
3. Copy the filenames with updated suffixes from the directory.
*/
+:root {
+ --nav-sub-mobile-padding: 8px;
+}
+
/* See FiraSans-LICENSE.txt for the Fira Sans license. */
@font-face {
font-family: 'Fira Sans';
@@ -87,21 +91,6 @@
box-sizing: border-box;
}
-/* This part handles the "default" theme being used depending on the system one. */
-html {
- content: "";
-}
-@media (prefers-color-scheme: light) {
- html {
- content: "light";
- }
-}
-@media (prefers-color-scheme: dark) {
- html {
- content: "dark";
- }
-}
-
/* General structure and fonts */
body {
@@ -217,7 +206,7 @@ ul.all-items {
a.anchor,
.small-section-header a,
#source-sidebar a,
-pre.rust a,
+.rust a,
.sidebar h2 a,
.sidebar h3 a,
.mobile-topbar h2 a,
@@ -228,43 +217,43 @@ h1 a,
color: var(--main-color);
}
-.content span.enum, .content a.enum,
-.content span.struct, .content a.struct,
-.content span.union, .content a.union,
-.content span.primitive, .content a.primitive,
-.content span.type, .content a.type,
-.content span.foreigntype, .content a.foreigntype {
+span.enum, a.enum,
+span.struct, a.struct,
+span.union, a.union,
+span.primitive, a.primitive,
+span.type, a.type,
+span.foreigntype, a.foreigntype {
color: var(--type-link-color);
}
-.content span.trait, .content a.trait,
-.content span.traitalias, .content a.traitalias {
+span.trait, a.trait,
+span.traitalias, a.traitalias {
color: var(--trait-link-color);
}
-.content span.associatedtype, .content a.associatedtype,
-.content span.constant, .content a.constant,
-.content span.static, .content a.static {
+span.associatedtype, a.associatedtype,
+span.constant, a.constant,
+span.static, a.static {
color: var(--assoc-item-link-color);
}
-.content span.fn, .content a.fn,
-.content span.method, .content a.method,
-.content span.tymethod, .content a.tymethod {
+span.fn, a.fn,
+span.method, a.method,
+span.tymethod, a.tymethod {
color: var(--function-link-color);
}
-.content span.attr, .content a.attr,
-.content span.derive, .content a.derive,
-.content span.macro, .content a.macro {
+span.attr, a.attr,
+span.derive, a.derive,
+span.macro, a.macro {
color: var(--macro-link-color);
}
-.content span.mod, .content a.mod {
+span.mod, a.mod {
color: var(--mod-link-color);
}
-.content span.keyword, .content a.keyword {
+span.keyword, a.keyword {
color: var(--keyword-link-color);
}
@@ -363,7 +352,7 @@ pre.item-decl {
.source .content pre {
padding: 20px;
}
-.rustdoc.source .example-wrap > pre.src-line-numbers {
+.rustdoc.source .example-wrap pre.src-line-numbers {
padding: 20px 0 20px 4px;
}
@@ -395,6 +384,7 @@ img {
font-size: 0.875rem;
flex: 0 0 200px;
overflow-y: scroll;
+ overscroll-behavior: contain;
position: sticky;
height: 100vh;
top: 0;
@@ -407,6 +397,7 @@ img {
overflow-x: hidden;
/* The sidebar is by default hidden */
overflow-y: hidden;
+ z-index: 1;
}
.sidebar, .mobile-topbar, .sidebar-menu-toggle,
@@ -547,14 +538,17 @@ ul.block, .block li {
margin-bottom: 0px;
}
-.rustdoc .example-wrap > pre {
+.rustdoc .example-wrap pre {
margin: 0;
flex-grow: 1;
+}
+
+.rustdoc:not(.source) .example-wrap pre {
overflow: auto hidden;
}
-.rustdoc .example-wrap > pre.example-line-numbers,
-.rustdoc .example-wrap > pre.src-line-numbers {
+.rustdoc .example-wrap pre.example-line-numbers,
+.rustdoc .example-wrap pre.src-line-numbers {
flex-grow: 0;
min-width: fit-content; /* prevent collapsing into nothing in truncated scraped examples */
overflow: initial;
@@ -565,7 +559,7 @@ ul.block, .block li {
color: var(--src-line-numbers-span-color);
}
-.rustdoc .example-wrap > pre.src-line-numbers {
+.rustdoc .example-wrap pre.src-line-numbers {
padding: 14px 0;
}
.src-line-numbers a, .src-line-numbers span {
@@ -713,7 +707,7 @@ h2.small-section-header > .anchor {
}
.main-heading a:hover,
-.example-wrap > pre.rust a:hover,
+.example-wrap .rust a:hover,
.all-items a:hover,
.docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,
.docblock-short a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,
@@ -1538,7 +1532,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 storage.js
+in source-script.js
*/
@media (max-width: 700px) {
/* When linking to an item with an `id` (for instance, by clicking a link in the sidebar,
@@ -1737,7 +1731,7 @@ in storage.js
.source nav.sub {
margin: 0;
- padding: 8px;
+ padding: var(--nav-sub-mobile-padding);
}
}
@@ -1794,6 +1788,7 @@ in storage.js
.sub-logo-container > img {
height: 35px;
width: 35px;
+ margin-bottom: var(--nav-sub-mobile-padding);
}
}
diff --git a/src/librustdoc/html/static/css/settings.css b/src/librustdoc/html/static/css/settings.css
index 920f45c4b..d13c783d2 100644
--- a/src/librustdoc/html/static/css/settings.css
+++ b/src/librustdoc/html/static/css/settings.css
@@ -8,7 +8,7 @@
height: 1.2rem;
width: 1.2rem;
color: inherit;
- border: 1px solid currentColor;
+ border: 2px solid var(--settings-input-border-color);
outline: none;
-webkit-appearance: none;
cursor: pointer;
@@ -52,6 +52,7 @@
}
.setting-check input:checked {
background-color: var(--settings-input-color);
+ border-width: 1px;
}
.setting-radio input:focus, .setting-check input:focus {
box-shadow: 0 0 1px 1px var(--settings-input-color);
diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css
index 90cf689ad..7145baad2 100644
--- a/src/librustdoc/html/static/css/themes/ayu.css
+++ b/src/librustdoc/html/static/css/themes/ayu.css
@@ -7,6 +7,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
--main-background-color: #0f1419;
--main-color: #c5c5c5;
--settings-input-color: #ffb454;
+ --settings-input-border-color: #999;
--settings-button-color: #fff;
--settings-button-border-focus: #e0e0e0;
--sidebar-background-color: #14191f;
diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css
index e8cd06931..3c1186a56 100644
--- a/src/librustdoc/html/static/css/themes/dark.css
+++ b/src/librustdoc/html/static/css/themes/dark.css
@@ -2,6 +2,7 @@
--main-background-color: #353535;
--main-color: #ddd;
--settings-input-color: #2196f3;
+ --settings-input-border-color: #999;
--settings-button-color: #000;
--settings-button-border-focus: #ffb900;
--sidebar-background-color: #505050;
diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css
index 5e3f14e48..f8c287137 100644
--- a/src/librustdoc/html/static/css/themes/light.css
+++ b/src/librustdoc/html/static/css/themes/light.css
@@ -2,6 +2,7 @@
--main-background-color: white;
--main-color: black;
--settings-input-color: #2196f3;
+ --settings-input-border-color: #717171;
--settings-button-color: #000;
--settings-button-border-focus: #717171;
--sidebar-background-color: #F5F5F5;
diff --git a/src/librustdoc/html/static/fonts/FiraSans-LICENSE.txt b/src/librustdoc/html/static/fonts/FiraSans-LICENSE.txt
index ff9afab06..d7e9c149b 100644
--- a/src/librustdoc/html/static/fonts/FiraSans-LICENSE.txt
+++ b/src/librustdoc/html/static/fonts/FiraSans-LICENSE.txt
@@ -1,3 +1,5 @@
+// REUSE-IgnoreStart
+
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
with Reserved Font Name < Fira >,
@@ -92,3 +94,5 @@ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+
+// REUSE-IgnoreEnd
diff --git a/src/librustdoc/html/static/fonts/NanumBarunGothic-LICENSE.txt b/src/librustdoc/html/static/fonts/NanumBarunGothic-LICENSE.txt
index 0bf46682b..4b3edc29e 100644
--- a/src/librustdoc/html/static/fonts/NanumBarunGothic-LICENSE.txt
+++ b/src/librustdoc/html/static/fonts/NanumBarunGothic-LICENSE.txt
@@ -1,3 +1,5 @@
+// REUSE-IgnoreStart
+
Copyright (c) 2010, NAVER Corporation (https://www.navercorp.com/),
with Reserved Font Name Nanum, Naver Nanum, NanumGothic, Naver NanumGothic,
@@ -97,3 +99,5 @@ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+
+// REUSE-IgnoreEnd
diff --git a/src/librustdoc/html/static/fonts/SourceCodePro-LICENSE.txt b/src/librustdoc/html/static/fonts/SourceCodePro-LICENSE.txt
index 07542572e..0d2941e14 100644
--- a/src/librustdoc/html/static/fonts/SourceCodePro-LICENSE.txt
+++ b/src/librustdoc/html/static/fonts/SourceCodePro-LICENSE.txt
@@ -1,3 +1,5 @@
+// REUSE-IgnoreStart
+
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
@@ -91,3 +93,5 @@ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+
+// REUSE-IgnoreEnd
diff --git a/src/librustdoc/html/static/fonts/SourceSerif4-LICENSE.md b/src/librustdoc/html/static/fonts/SourceSerif4-LICENSE.md
index 5871e1f3d..175fa4f47 100644
--- a/src/librustdoc/html/static/fonts/SourceSerif4-LICENSE.md
+++ b/src/librustdoc/html/static/fonts/SourceSerif4-LICENSE.md
@@ -1,3 +1,6 @@
+<!-- REUSE-IgnoreStart -->
+
+Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
Copyright 2014 - 2023 Adobe (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
@@ -91,3 +94,5 @@ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+
+<!-- REUSE-IgnoreEnd -->
diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
index ecbe15a59..4c81a0979 100644
--- a/src/librustdoc/html/static/js/externs.js
+++ b/src/librustdoc/html/static/js/externs.js
@@ -66,6 +66,11 @@ let Row;
let ResultsTable;
/**
+ * @typedef {Map<String, ResultObject>}
+ */
+let Results;
+
+/**
* @typedef {{
* desc: string,
* displayPath: string,
@@ -80,7 +85,7 @@ let ResultsTable;
* ty: number,
* }}
*/
-let Results;
+let ResultObject;
/**
* A pair of [inputs, outputs], or 0 for null. This is stored in the search index.
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 5e8c0e8d1..6f5987e68 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -1,20 +1,9 @@
// Local js definitions:
/* global addClass, getSettingValue, hasClass, searchState */
-/* global onEach, onEachLazy, removeClass */
+/* global onEach, onEachLazy, removeClass, getVar */
"use strict";
-// Get a value from the rustdoc-vars div, which is used to convey data from
-// Rust to the JS. If there is no such element, return null.
-function getVar(name) {
- const el = document.getElementById("rustdoc-vars");
- if (el) {
- return el.attributes["data-" + name].value;
- } else {
- return null;
- }
-}
-
// Given a basename (e.g. "storage") and an extension (e.g. ".js"), return a URL
// for a resource under the root-path, with the resource-suffix.
function resourcePath(basename, extension) {
@@ -187,6 +176,15 @@ function loadCss(cssUrl) {
document.getElementsByTagName("head")[0].appendChild(link);
}
+function preLoadCss(cssUrl) {
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload
+ const link = document.createElement("link");
+ link.href = cssUrl;
+ link.rel = "preload";
+ link.as = "style";
+ document.getElementsByTagName("head")[0].appendChild(link);
+}
+
(function() {
const isHelpPage = window.location.pathname.endsWith("/help.html");
@@ -207,6 +205,23 @@ function loadCss(cssUrl) {
// hopefully be loaded when the JS will generate the settings content.
loadCss(getVar("static-root-path") + getVar("settings-css"));
loadScript(getVar("static-root-path") + getVar("settings-js"));
+ preLoadCss(getVar("static-root-path") + getVar("theme-light-css"));
+ preLoadCss(getVar("static-root-path") + getVar("theme-dark-css"));
+ preLoadCss(getVar("static-root-path") + getVar("theme-ayu-css"));
+ // Pre-load all theme CSS files, so that switching feels seamless.
+ //
+ // When loading settings.html as a standalone page, the equivalent HTML is
+ // generated in context.rs.
+ setTimeout(() => {
+ const themes = getVar("themes").split(",");
+ for (const theme of themes) {
+ // if there are no themes, do nothing
+ // "".split(",") == [""]
+ if (theme !== "") {
+ preLoadCss(getVar("root-path") + theme + ".css");
+ }
+ }
+ }, 0);
};
window.searchState = {
@@ -311,16 +326,6 @@ function loadCss(cssUrl) {
},
};
- function getPageId() {
- if (window.location.hash) {
- const tmp = window.location.hash.replace(/^#/, "");
- if (tmp.length > 0) {
- return tmp;
- }
- }
- return null;
- }
-
const toggleAllDocsId = "toggle-all-docs";
let savedHash = "";
@@ -341,12 +346,12 @@ function loadCss(cssUrl) {
}
}
// This part is used in case an element is not visible.
- if (savedHash !== window.location.hash) {
- savedHash = window.location.hash;
- if (savedHash.length === 0) {
- return;
+ const pageId = window.location.hash.replace(/^#/, "");
+ if (savedHash !== pageId) {
+ savedHash = pageId;
+ if (pageId !== "") {
+ expandSection(pageId);
}
- expandSection(savedHash.slice(1)); // we remove the '#'
}
}
@@ -685,11 +690,6 @@ function loadCss(cssUrl) {
}
});
-
- const pageId = getPageId();
- if (pageId !== null) {
- expandSection(pageId);
- }
}());
window.rustdoc_add_line_numbers_to_examples = () => {
@@ -725,65 +725,18 @@ function loadCss(cssUrl) {
window.rustdoc_add_line_numbers_to_examples();
}
- let oldSidebarScrollPosition = null;
-
- // Scroll locking used both here and in source-script.js
-
- window.rustdocMobileScrollLock = function() {
- const mobile_topbar = document.querySelector(".mobile-topbar");
- if (window.innerWidth <= window.RUSTDOC_MOBILE_BREAKPOINT) {
- // This is to keep the scroll position on mobile.
- oldSidebarScrollPosition = window.scrollY;
- document.body.style.width = `${document.body.offsetWidth}px`;
- document.body.style.position = "fixed";
- document.body.style.top = `-${oldSidebarScrollPosition}px`;
- if (mobile_topbar) {
- mobile_topbar.style.top = `${oldSidebarScrollPosition}px`;
- mobile_topbar.style.position = "relative";
- }
- } else {
- oldSidebarScrollPosition = null;
- }
- };
-
- window.rustdocMobileScrollUnlock = function() {
- const mobile_topbar = document.querySelector(".mobile-topbar");
- if (oldSidebarScrollPosition !== null) {
- // This is to keep the scroll position on mobile.
- document.body.style.width = "";
- document.body.style.position = "";
- document.body.style.top = "";
- if (mobile_topbar) {
- mobile_topbar.style.top = "";
- mobile_topbar.style.position = "";
- }
- // The scroll position is lost when resetting the style, hence why we store it in
- // `oldSidebarScrollPosition`.
- window.scrollTo(0, oldSidebarScrollPosition);
- oldSidebarScrollPosition = null;
- }
- };
-
function showSidebar() {
window.hideAllModals(false);
- window.rustdocMobileScrollLock();
const sidebar = document.getElementsByClassName("sidebar")[0];
addClass(sidebar, "shown");
}
function hideSidebar() {
- window.rustdocMobileScrollUnlock();
const sidebar = document.getElementsByClassName("sidebar")[0];
removeClass(sidebar, "shown");
}
window.addEventListener("resize", () => {
- if (window.innerWidth > window.RUSTDOC_MOBILE_BREAKPOINT &&
- oldSidebarScrollPosition !== null) {
- // If the user opens the sidebar in "mobile" mode, and then grows the browser window,
- // we need to switch away from mobile mode and make the main content area scrollable.
- hideSidebar();
- }
if (window.CURRENT_TOOLTIP_ELEMENT) {
// As a workaround to the behavior of `contains: layout` used in doc togglers,
// tooltip popovers are positioned using javascript.
@@ -996,9 +949,7 @@ function loadCss(cssUrl) {
<code>enum</code>, <code>trait</code>, <code>type</code>, <code>macro</code>, \
and <code>const</code>.",
"Search functions by type signature (e.g., <code>vec -&gt; usize</code> or \
- <code>-&gt; vec</code>)",
- "Search multiple things at once by splitting your query with comma (e.g., \
- <code>str,u8</code> or <code>String,struct:Vec,test</code>)",
+ <code>-&gt; vec</code> or <code>String, enum:Cow -&gt; bool</code>)",
"You can look for items with an exact name by putting double quotes around \
your request: <code>\"string\"</code>",
"Look for items inside another one by searching for a path: <code>vec::Vec</code>",
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index b98bced41..929dae81c 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -76,39 +76,111 @@ function printTab(nb) {
}
/**
- * A function to compute the Levenshtein distance between two strings
- * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported
- * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode
- * This code is an unmodified version of the code written by Marco de Wit
- * and was found at https://stackoverflow.com/a/18514751/745719
+ * The [edit distance] is a metric for measuring the difference between two strings.
+ *
+ * [edit distance]: https://en.wikipedia.org/wiki/Edit_distance
*/
-const levenshtein_row2 = [];
-function levenshtein(s1, s2) {
- if (s1 === s2) {
- return 0;
- }
- const s1_len = s1.length, s2_len = s2.length;
- if (s1_len && s2_len) {
- let i1 = 0, i2 = 0, a, b, c, c2;
- const row = levenshtein_row2;
- while (i1 < s1_len) {
- row[i1] = ++i1;
- }
- while (i2 < s2_len) {
- c2 = s2.charCodeAt(i2);
- a = i2;
- ++i2;
- b = i2;
- for (i1 = 0; i1 < s1_len; ++i1) {
- c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
- a = row[i1];
- b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
- row[i1] = b;
- }
- }
- return b;
- }
- return s1_len + s2_len;
+
+/*
+ * This function was translated, mostly line-for-line, from
+ * https://github.com/rust-lang/rust/blob/ff4b772f805ec1e/compiler/rustc_span/src/edit_distance.rs
+ *
+ * The current implementation is the restricted Damerau-Levenshtein algorithm. It is restricted
+ * because it does not permit modifying characters that have already been transposed. The specific
+ * algorithm should not matter to the caller of the methods, which is why it is not noted in the
+ * documentation.
+ */
+const editDistanceState = {
+ current: [],
+ prev: [],
+ prevPrev: [],
+ calculate: function calculate(a, b, limit) {
+ // Ensure that `b` is the shorter string, minimizing memory use.
+ if (a.length < b.length) {
+ const aTmp = a;
+ a = b;
+ b = aTmp;
+ }
+
+ const minDist = a.length - b.length;
+ // If we know the limit will be exceeded, we can return early.
+ if (minDist > limit) {
+ return limit + 1;
+ }
+
+ // Strip common prefix.
+ // We know that `b` is the shorter string, so we don't need to check
+ // `a.length`.
+ while (b.length > 0 && b[0] === a[0]) {
+ a = a.substring(1);
+ b = b.substring(1);
+ }
+ // Strip common suffix.
+ while (b.length > 0 && b[b.length - 1] === a[a.length - 1]) {
+ a = a.substring(0, a.length - 1);
+ b = b.substring(0, b.length - 1);
+ }
+
+ // If either string is empty, the distance is the length of the other.
+ // We know that `b` is the shorter string, so we don't need to check `a`.
+ if (b.length === 0) {
+ return minDist;
+ }
+
+ const aLength = a.length;
+ const bLength = b.length;
+
+ for (let i = 0; i <= bLength; ++i) {
+ this.current[i] = 0;
+ this.prev[i] = i;
+ this.prevPrev[i] = Number.MAX_VALUE;
+ }
+
+ // row by row
+ for (let i = 1; i <= aLength; ++i) {
+ this.current[0] = i;
+ const aIdx = i - 1;
+
+ // column by column
+ for (let j = 1; j <= bLength; ++j) {
+ const bIdx = j - 1;
+
+ // There is no cost to substitute a character with itself.
+ const substitutionCost = a[aIdx] === b[bIdx] ? 0 : 1;
+
+ this.current[j] = Math.min(
+ // deletion
+ this.prev[j] + 1,
+ // insertion
+ this.current[j - 1] + 1,
+ // substitution
+ this.prev[j - 1] + substitutionCost
+ );
+
+ if ((i > 1) && (j > 1) && (a[aIdx] === b[bIdx - 1]) && (a[aIdx - 1] === b[bIdx])) {
+ // transposition
+ this.current[j] = Math.min(
+ this.current[j],
+ this.prevPrev[j - 2] + 1
+ );
+ }
+ }
+
+ // Rotate the buffers, reusing the memory
+ const prevPrevTmp = this.prevPrev;
+ this.prevPrev = this.prev;
+ this.prev = this.current;
+ this.current = prevPrevTmp;
+ }
+
+ // `prev` because we already rotated the buffers.
+ const distance = this.prev[bLength];
+ return distance <= limit ? distance : (limit + 1);
+ },
+};
+
+function editDistance(a, b, limit) {
+ return editDistanceState.calculate(a, b, limit);
}
function initSearch(rawSearchIndex) {
@@ -119,7 +191,7 @@ function initSearch(rawSearchIndex) {
*/
let searchIndex;
let currentResults;
- const ALIASES = Object.create(null);
+ const ALIASES = new Map();
function isWhitespace(c) {
return " \t\n\r".indexOf(c) !== -1;
@@ -282,12 +354,15 @@ function initSearch(rawSearchIndex) {
if (isInGenerics) {
parserState.genericsElems += 1;
}
+ const typeFilter = parserState.typeFilter;
+ parserState.typeFilter = null;
return {
name: name,
fullPath: pathSegments,
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
pathLast: pathSegments[pathSegments.length - 1],
generics: generics,
+ typeFilter,
};
}
@@ -386,9 +461,7 @@ function initSearch(rawSearchIndex) {
if (parserState.pos < parserState.length &&
parserState.userQuery[parserState.pos] === "<"
) {
- if (isInGenerics) {
- throw ["Unexpected ", "<", " after ", "<"];
- } else if (start >= end) {
+ if (start >= end) {
throw ["Found generics without a path"];
}
parserState.pos += 1;
@@ -423,6 +496,11 @@ function initSearch(rawSearchIndex) {
*/
function getItemsBefore(query, parserState, elems, endChar) {
let foundStopChar = true;
+ let start = parserState.pos;
+
+ // If this is a generic, keep the outer item's type filter around.
+ const oldTypeFilter = parserState.typeFilter;
+ parserState.typeFilter = null;
while (parserState.pos < parserState.length) {
const c = parserState.userQuery[parserState.pos];
@@ -434,7 +512,25 @@ function initSearch(rawSearchIndex) {
continue;
} else if (c === ":" && isPathStart(parserState)) {
throw ["Unexpected ", "::", ": paths cannot start with ", "::"];
- } else if (c === ":" || isEndCharacter(c)) {
+ } else if (c === ":") {
+ if (parserState.typeFilter !== null) {
+ throw ["Unexpected ", ":"];
+ }
+ if (elems.length === 0) {
+ throw ["Expected type filter before ", ":"];
+ } else if (query.literalSearch) {
+ throw ["You cannot use quotes on type filter"];
+ }
+ // The type filter doesn't count as an element since it's a modifier.
+ const typeFilterElem = elems.pop();
+ checkExtraTypeFilterCharacters(start, parserState);
+ parserState.typeFilter = typeFilterElem.name;
+ parserState.pos += 1;
+ parserState.totalElems -= 1;
+ query.literalSearch = false;
+ foundStopChar = true;
+ continue;
+ } else if (isEndCharacter(c)) {
let extra = "";
if (endChar === ">") {
extra = "<";
@@ -468,15 +564,10 @@ function initSearch(rawSearchIndex) {
];
}
const posBefore = parserState.pos;
+ start = parserState.pos;
getNextElem(query, parserState, elems, endChar === ">");
- if (endChar !== "") {
- if (parserState.pos >= parserState.length) {
- throw ["Unclosed ", "<"];
- }
- const c2 = parserState.userQuery[parserState.pos];
- if (!isSeparatorCharacter(c2) && c2 !== endChar) {
- throw ["Expected ", endChar, ", found ", c2];
- }
+ if (endChar !== "" && parserState.pos >= parserState.length) {
+ throw ["Unclosed ", "<"];
}
// This case can be encountered if `getNextElem` encountered a "stop character" right
// from the start. For example if you have `,,` or `<>`. In this case, we simply move up
@@ -492,6 +583,8 @@ function initSearch(rawSearchIndex) {
// We are either at the end of the string or on the `endChar` character, let's move forward
// in any case.
parserState.pos += 1;
+
+ parserState.typeFilter = oldTypeFilter;
}
/**
@@ -500,10 +593,10 @@ function initSearch(rawSearchIndex) {
*
* @param {ParserState} parserState
*/
- function checkExtraTypeFilterCharacters(parserState) {
+ function checkExtraTypeFilterCharacters(start, parserState) {
const query = parserState.userQuery;
- for (let pos = 0; pos < parserState.pos; ++pos) {
+ for (let pos = start; pos < parserState.pos; ++pos) {
if (!isIdentCharacter(query[pos]) && !isWhitespaceCharacter(query[pos])) {
throw ["Unexpected ", query[pos], " in type filter"];
}
@@ -519,6 +612,7 @@ function initSearch(rawSearchIndex) {
*/
function parseInput(query, parserState) {
let foundStopChar = true;
+ let start = parserState.pos;
while (parserState.pos < parserState.length) {
const c = parserState.userQuery[parserState.pos];
@@ -540,16 +634,15 @@ function initSearch(rawSearchIndex) {
}
if (query.elems.length === 0) {
throw ["Expected type filter before ", ":"];
- } else if (query.elems.length !== 1 || parserState.totalElems !== 1) {
- throw ["Unexpected ", ":"];
} else if (query.literalSearch) {
throw ["You cannot use quotes on type filter"];
}
- checkExtraTypeFilterCharacters(parserState);
// The type filter doesn't count as an element since it's a modifier.
- parserState.typeFilter = query.elems.pop().name;
+ const typeFilterElem = query.elems.pop();
+ checkExtraTypeFilterCharacters(start, parserState);
+ parserState.typeFilter = typeFilterElem.name;
parserState.pos += 1;
- parserState.totalElems = 0;
+ parserState.totalElems -= 1;
query.literalSearch = false;
foundStopChar = true;
continue;
@@ -581,6 +674,7 @@ function initSearch(rawSearchIndex) {
];
}
const before = query.elems.length;
+ start = parserState.pos;
getNextElem(query, parserState, query.elems, false);
if (query.elems.length === before) {
// Nothing was added, weird... Let's increase the position to not remain stuck.
@@ -588,6 +682,9 @@ function initSearch(rawSearchIndex) {
}
foundStopChar = false;
}
+ if (parserState.typeFilter !== null) {
+ throw ["Unexpected ", ":", " (expected path after type filter)"];
+ }
while (parserState.pos < parserState.length) {
if (isReturnArrow(parserState)) {
parserState.pos += 2;
@@ -615,7 +712,6 @@ function initSearch(rawSearchIndex) {
return {
original: userQuery,
userQuery: userQuery.toLowerCase(),
- typeFilter: NO_TYPE_FILTER,
elems: [],
returned: [],
// Total number of "top" elements (does not include generics).
@@ -666,18 +762,15 @@ function initSearch(rawSearchIndex) {
*
* ident = *(ALPHA / DIGIT / "_")
* path = ident *(DOUBLE-COLON ident) [!]
- * arg = path [generics]
- * arg-without-generic = path
+ * arg = [type-filter *WS COLON *WS] path [generics]
* type-sep = COMMA/WS *(COMMA/WS)
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
- * nonempty-arg-list-without-generics = *(type-sep) arg-without-generic
- * *(type-sep arg-without-generic) *(type-sep)
- * generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list-without-generics ] *(type-sep)
- * CLOSE-ANGLE-BRACKET/EOF
+ * generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
+ * CLOSE-ANGLE-BRACKET
* return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
*
* exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
- * type-search = [type-filter *WS COLON] [ nonempty-arg-list ] [ return-args ]
+ * type-search = [ nonempty-arg-list ] [ return-args ]
*
* query = *WS (exact-search / type-search) *WS
*
@@ -726,6 +819,20 @@ function initSearch(rawSearchIndex) {
* @return {ParsedQuery} - The parsed query
*/
function parseQuery(userQuery) {
+ function convertTypeFilterOnElem(elem) {
+ if (elem.typeFilter !== null) {
+ let typeFilter = elem.typeFilter;
+ if (typeFilter === "const") {
+ typeFilter = "constant";
+ }
+ elem.typeFilter = itemTypeFromName(typeFilter);
+ } else {
+ elem.typeFilter = NO_TYPE_FILTER;
+ }
+ for (const elem2 of elem.generics) {
+ convertTypeFilterOnElem(elem2);
+ }
+ }
userQuery = userQuery.trim();
const parserState = {
length: userQuery.length,
@@ -740,17 +847,15 @@ function initSearch(rawSearchIndex) {
try {
parseInput(query, parserState);
- if (parserState.typeFilter !== null) {
- let typeFilter = parserState.typeFilter;
- if (typeFilter === "const") {
- typeFilter = "constant";
- }
- query.typeFilter = itemTypeFromName(typeFilter);
+ for (const elem of query.elems) {
+ convertTypeFilterOnElem(elem);
+ }
+ for (const elem of query.returned) {
+ convertTypeFilterOnElem(elem);
}
} catch (err) {
query = newParsedQuery(userQuery);
query.error = err;
- query.typeFilter = -1;
return query;
}
@@ -793,26 +898,34 @@ function initSearch(rawSearchIndex) {
* @return {ResultsTable}
*/
function execQuery(parsedQuery, searchWords, filterCrates, currentCrate) {
- const results_others = {}, results_in_args = {}, results_returned = {};
+ const results_others = new Map(), results_in_args = new Map(),
+ results_returned = new Map();
+ /**
+ * Add extra data to result objects, and filter items that have been
+ * marked for removal.
+ *
+ * @param {[ResultObject]} results
+ * @returns {[ResultObject]}
+ */
function transformResults(results) {
- const duplicates = {};
+ const duplicates = new Set();
const out = [];
for (const result of results) {
if (result.id > -1) {
const obj = searchIndex[result.id];
- obj.lev = result.lev;
+ obj.dist = result.dist;
const res = buildHrefAndPath(obj);
obj.displayPath = pathSplitter(res[0]);
obj.fullPath = obj.displayPath + obj.name;
// To be sure than it some items aren't considered as duplicate.
obj.fullPath += "|" + obj.ty;
- if (duplicates[obj.fullPath]) {
+ if (duplicates.has(obj.fullPath)) {
continue;
}
- duplicates[obj.fullPath] = true;
+ duplicates.add(obj.fullPath);
obj.href = res[1];
out.push(obj);
@@ -824,24 +937,30 @@ function initSearch(rawSearchIndex) {
return out;
}
+ /**
+ * This function takes a result map, and sorts it by various criteria, including edit
+ * distance, substring match, and the crate it comes from.
+ *
+ * @param {Results} results
+ * @param {boolean} isType
+ * @param {string} preferredCrate
+ * @returns {[ResultObject]}
+ */
function sortResults(results, isType, preferredCrate) {
- const userQuery = parsedQuery.userQuery;
- const ar = [];
- for (const entry in results) {
- if (hasOwnPropertyRustdoc(results, entry)) {
- const result = results[entry];
- result.word = searchWords[result.id];
- result.item = searchIndex[result.id] || {};
- ar.push(result);
- }
- }
- results = ar;
// if there are no results then return to default and fail
- if (results.length === 0) {
+ if (results.size === 0) {
return [];
}
- results.sort((aaa, bbb) => {
+ const userQuery = parsedQuery.userQuery;
+ const result_list = [];
+ for (const result of results.values()) {
+ result.word = searchWords[result.id];
+ result.item = searchIndex[result.id] || {};
+ result_list.push(result);
+ }
+
+ result_list.sort((aaa, bbb) => {
let a, b;
// sort by exact match with regard to the last word (mismatch goes later)
@@ -860,8 +979,8 @@ function initSearch(rawSearchIndex) {
// Sort by distance in the path part, if specified
// (less changes required to match means higher rankings)
- a = aaa.path_lev;
- b = bbb.path_lev;
+ a = aaa.path_dist;
+ b = bbb.path_dist;
if (a !== b) {
return a - b;
}
@@ -875,8 +994,15 @@ function initSearch(rawSearchIndex) {
// Sort by distance in the name part, the last part of the path
// (less changes required to match means higher rankings)
- a = (aaa.lev);
- b = (bbb.lev);
+ a = (aaa.dist);
+ b = (bbb.dist);
+ if (a !== b) {
+ return a - b;
+ }
+
+ // sort deprecated items later
+ a = aaa.item.deprecated;
+ b = bbb.item.deprecated;
if (a !== b) {
return a - b;
}
@@ -943,7 +1069,7 @@ function initSearch(rawSearchIndex) {
nameSplit = hasPath ? null : parsedQuery.elems[0].path;
}
- for (const result of results) {
+ for (const result of result_list) {
// this validation does not make sense when searching by types
if (result.dontValidate) {
continue;
@@ -956,72 +1082,87 @@ function initSearch(rawSearchIndex) {
result.id = -1;
}
}
- return transformResults(results);
+ return transformResults(result_list);
}
/**
* This function checks if the object (`row`) generics match the given type (`elem`)
- * generics. If there are no generics on `row`, `defaultLev` is returned.
+ * generics. If there are no generics on `row`, `defaultDistance` is returned.
*
- * @param {Row} row - The object to check.
- * @param {QueryElement} elem - The element from the parsed query.
- * @param {integer} defaultLev - This is the value to return in case there are no generics.
+ * @param {Row} row - The object to check.
+ * @param {QueryElement} elem - The element from the parsed query.
+ * @param {integer} defaultDistance - This is the value to return in case there are no
+ * generics.
*
- * @return {integer} - Returns the best match (if any) or `maxLevDistance + 1`.
+ * @return {integer} - Returns the best match (if any) or `maxEditDistance + 1`.
*/
- function checkGenerics(row, elem, defaultLev, maxLevDistance) {
+ function checkGenerics(row, elem, defaultDistance, maxEditDistance) {
if (row.generics.length === 0) {
- return elem.generics.length === 0 ? defaultLev : maxLevDistance + 1;
+ return elem.generics.length === 0 ? defaultDistance : maxEditDistance + 1;
} else if (row.generics.length > 0 && row.generics[0].name === null) {
- return checkGenerics(row.generics[0], elem, defaultLev, maxLevDistance);
+ return checkGenerics(row.generics[0], elem, defaultDistance, maxEditDistance);
}
// The names match, but we need to be sure that all generics kinda
// match as well.
- let elem_name;
if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) {
- const elems = Object.create(null);
+ const elems = new Map();
for (const entry of row.generics) {
- elem_name = entry.name;
- if (elem_name === "") {
+ if (entry.name === "") {
// Pure generic, needs to check into it.
- if (checkGenerics(entry, elem, maxLevDistance + 1, maxLevDistance) !== 0) {
- return maxLevDistance + 1;
+ if (checkGenerics(entry, elem, maxEditDistance + 1, maxEditDistance)
+ !== 0) {
+ return maxEditDistance + 1;
}
continue;
}
- if (elems[elem_name] === undefined) {
- elems[elem_name] = 0;
+ let currentEntryElems;
+ if (elems.has(entry.name)) {
+ currentEntryElems = elems.get(entry.name);
+ } else {
+ currentEntryElems = [];
+ elems.set(entry.name, currentEntryElems);
}
- elems[elem_name] += 1;
+ currentEntryElems.push(entry);
}
// We need to find the type that matches the most to remove it in order
// to move forward.
- for (const generic of elem.generics) {
- let match = null;
- if (elems[generic.name]) {
- match = generic.name;
- } else {
- for (elem_name in elems) {
- if (!hasOwnPropertyRustdoc(elems, elem_name)) {
- continue;
- }
- if (elem_name === generic) {
- match = elem_name;
- break;
- }
+ const handleGeneric = generic => {
+ if (!elems.has(generic.name)) {
+ return false;
+ }
+ const matchElems = elems.get(generic.name);
+ const matchIdx = matchElems.findIndex(tmp_elem => {
+ if (checkGenerics(tmp_elem, generic, 0, maxEditDistance) !== 0) {
+ return false;
}
+ return typePassesFilter(generic.typeFilter, tmp_elem.ty);
+ });
+ if (matchIdx === -1) {
+ return false;
+ }
+ matchElems.splice(matchIdx, 1);
+ if (matchElems.length === 0) {
+ elems.delete(generic.name);
}
- if (match === null) {
- return maxLevDistance + 1;
+ return true;
+ };
+ // To do the right thing with type filters, we first process generics
+ // that have them, removing matching ones from the "bag," then do the
+ // ones with no type filter, which can match any entry regardless of its
+ // own type.
+ for (const generic of elem.generics) {
+ if (generic.typeFilter !== -1 && !handleGeneric(generic)) {
+ return maxEditDistance + 1;
}
- elems[match] -= 1;
- if (elems[match] === 0) {
- delete elems[match];
+ }
+ for (const generic of elem.generics) {
+ if (generic.typeFilter === -1 && !handleGeneric(generic)) {
+ return maxEditDistance + 1;
}
}
return 0;
}
- return maxLevDistance + 1;
+ return maxEditDistance + 1;
}
/**
@@ -1031,17 +1172,17 @@ function initSearch(rawSearchIndex) {
* @param {Row} row
* @param {QueryElement} elem - The element from the parsed query.
*
- * @return {integer} - Returns a Levenshtein distance to the best match.
+ * @return {integer} - Returns an edit distance to the best match.
*/
- function checkIfInGenerics(row, elem, maxLevDistance) {
- let lev = maxLevDistance + 1;
+ function checkIfInGenerics(row, elem, maxEditDistance) {
+ let dist = maxEditDistance + 1;
for (const entry of row.generics) {
- lev = Math.min(checkType(entry, elem, true, maxLevDistance), lev);
- if (lev === 0) {
+ dist = Math.min(checkType(entry, elem, true, maxEditDistance), dist);
+ if (dist === 0) {
break;
}
}
- return lev;
+ return dist;
}
/**
@@ -1052,67 +1193,73 @@ function initSearch(rawSearchIndex) {
* @param {QueryElement} elem - The element from the parsed query.
* @param {boolean} literalSearch
*
- * @return {integer} - Returns a Levenshtein distance to the best match. If there is
- * no match, returns `maxLevDistance + 1`.
+ * @return {integer} - Returns an edit distance to the best match. If there is
+ * no match, returns `maxEditDistance + 1`.
*/
- function checkType(row, elem, literalSearch, maxLevDistance) {
+ function checkType(row, elem, literalSearch, maxEditDistance) {
if (row.name === null) {
// This is a pure "generic" search, no need to run other checks.
if (row.generics.length > 0) {
- return checkIfInGenerics(row, elem, maxLevDistance);
+ return checkIfInGenerics(row, elem, maxEditDistance);
}
- return maxLevDistance + 1;
+ return maxEditDistance + 1;
}
- let lev = levenshtein(row.name, elem.name);
+ let dist;
+ if (typePassesFilter(elem.typeFilter, row.ty)) {
+ dist = editDistance(row.name, elem.name, maxEditDistance);
+ } else {
+ dist = maxEditDistance + 1;
+ }
if (literalSearch) {
- if (lev !== 0) {
+ if (dist !== 0) {
// The name didn't match, let's try to check if the generics do.
if (elem.generics.length === 0) {
const checkGeneric = row.generics.length > 0;
if (checkGeneric && row.generics
- .findIndex(tmp_elem => tmp_elem.name === elem.name) !== -1) {
+ .findIndex(tmp_elem => tmp_elem.name === elem.name &&
+ typePassesFilter(elem.typeFilter, tmp_elem.ty)) !== -1) {
return 0;
}
}
- return maxLevDistance + 1;
+ return maxEditDistance + 1;
} else if (elem.generics.length > 0) {
- return checkGenerics(row, elem, maxLevDistance + 1, maxLevDistance);
+ return checkGenerics(row, elem, maxEditDistance + 1, maxEditDistance);
}
return 0;
} else if (row.generics.length > 0) {
if (elem.generics.length === 0) {
- if (lev === 0) {
+ if (dist === 0) {
return 0;
}
// The name didn't match so we now check if the type we're looking for is inside
// the generics!
- lev = Math.min(lev, checkIfInGenerics(row, elem, maxLevDistance));
- return lev;
- } else if (lev > maxLevDistance) {
+ dist = Math.min(dist, checkIfInGenerics(row, elem, maxEditDistance));
+ return dist;
+ } else if (dist > maxEditDistance) {
// So our item's name doesn't match at all and has generics.
//
// Maybe it's present in a sub generic? For example "f<A<B<C>>>()", if we're
// looking for "B<C>", we'll need to go down.
- return checkIfInGenerics(row, elem, maxLevDistance);
+ return checkIfInGenerics(row, elem, maxEditDistance);
} else {
// At this point, the name kinda match and we have generics to check, so
// let's go!
- const tmp_lev = checkGenerics(row, elem, lev, maxLevDistance);
- if (tmp_lev > maxLevDistance) {
- return maxLevDistance + 1;
+ const tmp_dist = checkGenerics(row, elem, dist, maxEditDistance);
+ if (tmp_dist > maxEditDistance) {
+ return maxEditDistance + 1;
}
// We compute the median value of both checks and return it.
- return (tmp_lev + lev) / 2;
+ return (tmp_dist + dist) / 2;
}
} else if (elem.generics.length > 0) {
// In this case, we were expecting generics but there isn't so we simply reject this
// one.
- return maxLevDistance + 1;
+ return maxEditDistance + 1;
}
// No generics on our query or on the target type so we can return without doing
// anything else.
- return lev;
+ return dist;
}
/**
@@ -1120,29 +1267,42 @@ function initSearch(rawSearchIndex) {
*
* @param {Row} row
* @param {QueryElement} elem - The element from the parsed query.
- * @param {integer} typeFilter
+ * @param {integer} maxEditDistance
+ * @param {Array<integer>} skipPositions - Do not return one of these positions.
*
- * @return {integer} - Returns a Levenshtein distance to the best match. If there is no
- * match, returns `maxLevDistance + 1`.
+ * @return {dist: integer, position: integer} - Returns an edit distance to the best match.
+ * If there is no match, returns
+ * `maxEditDistance + 1` and position: -1.
*/
- function findArg(row, elem, typeFilter, maxLevDistance) {
- let lev = maxLevDistance + 1;
+ function findArg(row, elem, maxEditDistance, skipPositions) {
+ let dist = maxEditDistance + 1;
+ let position = -1;
if (row && row.type && row.type.inputs && row.type.inputs.length > 0) {
+ let i = 0;
for (const input of row.type.inputs) {
- if (!typePassesFilter(typeFilter, input.ty)) {
+ if (skipPositions.indexOf(i) !== -1) {
+ i += 1;
continue;
}
- lev = Math.min(
- lev,
- checkType(input, elem, parsedQuery.literalSearch, maxLevDistance)
+ const typeDist = checkType(
+ input,
+ elem,
+ parsedQuery.literalSearch,
+ maxEditDistance
);
- if (lev === 0) {
- return 0;
+ if (typeDist === 0) {
+ return {dist: 0, position: i};
}
+ if (typeDist < dist) {
+ dist = typeDist;
+ position = i;
+ }
+ i += 1;
}
}
- return parsedQuery.literalSearch ? maxLevDistance + 1 : lev;
+ dist = parsedQuery.literalSearch ? maxEditDistance + 1 : dist;
+ return {dist, position};
}
/**
@@ -1150,37 +1310,50 @@ function initSearch(rawSearchIndex) {
*
* @param {Row} row
* @param {QueryElement} elem - The element from the parsed query.
- * @param {integer} typeFilter
+ * @param {integer} maxEditDistance
+ * @param {Array<integer>} skipPositions - Do not return one of these positions.
*
- * @return {integer} - Returns a Levenshtein distance to the best match. If there is no
- * match, returns `maxLevDistance + 1`.
+ * @return {dist: integer, position: integer} - Returns an edit distance to the best match.
+ * If there is no match, returns
+ * `maxEditDistance + 1` and position: -1.
*/
- function checkReturned(row, elem, typeFilter, maxLevDistance) {
- let lev = maxLevDistance + 1;
+ function checkReturned(row, elem, maxEditDistance, skipPositions) {
+ let dist = maxEditDistance + 1;
+ let position = -1;
if (row && row.type && row.type.output.length > 0) {
const ret = row.type.output;
+ let i = 0;
for (const ret_ty of ret) {
- if (!typePassesFilter(typeFilter, ret_ty.ty)) {
+ if (skipPositions.indexOf(i) !== -1) {
+ i += 1;
continue;
}
- lev = Math.min(
- lev,
- checkType(ret_ty, elem, parsedQuery.literalSearch, maxLevDistance)
+ const typeDist = checkType(
+ ret_ty,
+ elem,
+ parsedQuery.literalSearch,
+ maxEditDistance
);
- if (lev === 0) {
- return 0;
+ if (typeDist === 0) {
+ return {dist: 0, position: i};
}
+ if (typeDist < dist) {
+ dist = typeDist;
+ position = i;
+ }
+ i += 1;
}
}
- return parsedQuery.literalSearch ? maxLevDistance + 1 : lev;
+ dist = parsedQuery.literalSearch ? maxEditDistance + 1 : dist;
+ return {dist, position};
}
- function checkPath(contains, ty, maxLevDistance) {
+ function checkPath(contains, ty, maxEditDistance) {
if (contains.length === 0) {
return 0;
}
- let ret_lev = maxLevDistance + 1;
+ let ret_dist = maxEditDistance + 1;
const path = ty.path.split("::");
if (ty.parent && ty.parent.name) {
@@ -1190,27 +1363,27 @@ function initSearch(rawSearchIndex) {
const length = path.length;
const clength = contains.length;
if (clength > length) {
- return maxLevDistance + 1;
+ return maxEditDistance + 1;
}
for (let i = 0; i < length; ++i) {
if (i + clength > length) {
break;
}
- let lev_total = 0;
+ let dist_total = 0;
let aborted = false;
for (let x = 0; x < clength; ++x) {
- const lev = levenshtein(path[i + x], contains[x]);
- if (lev > maxLevDistance) {
+ const dist = editDistance(path[i + x], contains[x], maxEditDistance);
+ if (dist > maxEditDistance) {
aborted = true;
break;
}
- lev_total += lev;
+ dist_total += dist;
}
if (!aborted) {
- ret_lev = Math.min(ret_lev, Math.round(lev_total / clength));
+ ret_dist = Math.min(ret_dist, Math.round(dist_total / clength));
}
}
- return ret_lev;
+ return ret_dist;
}
function typePassesFilter(filter, type) {
@@ -1244,6 +1417,7 @@ function initSearch(rawSearchIndex) {
parent: item.parent,
type: item.type,
is_alias: true,
+ deprecated: item.deprecated,
};
}
@@ -1254,22 +1428,22 @@ function initSearch(rawSearchIndex) {
const aliases = [];
const crateAliases = [];
if (filterCrates !== null) {
- if (ALIASES[filterCrates] && ALIASES[filterCrates][lowerQuery]) {
- const query_aliases = ALIASES[filterCrates][lowerQuery];
+ if (ALIASES.has(filterCrates) && ALIASES.get(filterCrates).has(lowerQuery)) {
+ const query_aliases = ALIASES.get(filterCrates).get(lowerQuery);
for (const alias of query_aliases) {
aliases.push(createAliasFromItem(searchIndex[alias]));
}
}
} else {
- Object.keys(ALIASES).forEach(crate => {
- if (ALIASES[crate][lowerQuery]) {
+ for (const [crate, crateAliasesIndex] of ALIASES) {
+ if (crateAliasesIndex.has(lowerQuery)) {
const pushTo = crate === currentCrate ? crateAliases : aliases;
- const query_aliases = ALIASES[crate][lowerQuery];
+ const query_aliases = crateAliasesIndex.get(lowerQuery);
for (const alias of query_aliases) {
pushTo.push(createAliasFromItem(searchIndex[alias]));
}
}
- });
+ }
}
const sortFunc = (aaa, bbb) => {
@@ -1304,41 +1478,41 @@ function initSearch(rawSearchIndex) {
* This function adds the given result into the provided `results` map if it matches the
* following condition:
*
- * * If it is a "literal search" (`parsedQuery.literalSearch`), then `lev` must be 0.
- * * If it is not a "literal search", `lev` must be <= `maxLevDistance`.
+ * * If it is a "literal search" (`parsedQuery.literalSearch`), then `dist` must be 0.
+ * * If it is not a "literal search", `dist` must be <= `maxEditDistance`.
*
* 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.
* * `index` is an `integer`` used to sort by the position of the word in the item's name.
- * * `lev` is the main metric used to sort the search results.
- * * `path_lev` is zero if a single-component search query is used, otherwise it's the
+ * * `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
* distance computed for everything other than the last path component.
*
* @param {Results} results
* @param {string} fullId
* @param {integer} id
* @param {integer} index
- * @param {integer} lev
- * @param {integer} path_lev
+ * @param {integer} dist
+ * @param {integer} path_dist
*/
- function addIntoResults(results, fullId, id, index, lev, path_lev, maxLevDistance) {
- const inBounds = lev <= maxLevDistance || index !== -1;
- if (lev === 0 || (!parsedQuery.literalSearch && inBounds)) {
- if (results[fullId] !== undefined) {
- const result = results[fullId];
- if (result.dontValidate || result.lev <= lev) {
+ function addIntoResults(results, fullId, id, index, dist, path_dist, maxEditDistance) {
+ const inBounds = dist <= maxEditDistance || index !== -1;
+ if (dist === 0 || (!parsedQuery.literalSearch && inBounds)) {
+ if (results.has(fullId)) {
+ const result = results.get(fullId);
+ if (result.dontValidate || result.dist <= dist) {
return;
}
}
- results[fullId] = {
+ results.set(fullId, {
id: id,
index: index,
dontValidate: parsedQuery.literalSearch,
- lev: lev,
- path_lev: path_lev,
- };
+ dist: dist,
+ path_dist: path_dist,
+ });
}
}
@@ -1346,7 +1520,7 @@ function initSearch(rawSearchIndex) {
* This function is called in case the query is only one element (with or without generics).
* This element will be compared to arguments' and returned values' items and also to items.
*
- * Other important thing to note: since there is only one element, we use levenshtein
+ * Other important thing to note: since there is only one element, we use edit
* distance for name comparisons.
*
* @param {Row} row
@@ -1364,24 +1538,24 @@ function initSearch(rawSearchIndex) {
results_others,
results_in_args,
results_returned,
- maxLevDistance
+ maxEditDistance
) {
if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
return;
}
- let lev, index = -1, path_lev = 0;
+ let dist, index = -1, path_dist = 0;
const fullId = row.id;
const searchWord = searchWords[pos];
- const in_args = findArg(row, elem, parsedQuery.typeFilter, maxLevDistance);
- const returned = checkReturned(row, elem, parsedQuery.typeFilter, maxLevDistance);
+ const in_args = findArg(row, elem, maxEditDistance, []);
+ const returned = checkReturned(row, elem, maxEditDistance, []);
- // path_lev is 0 because no parent path information is currently stored
+ // path_dist is 0 because no parent path information is currently stored
// in the search index
- addIntoResults(results_in_args, fullId, pos, -1, in_args, 0, maxLevDistance);
- addIntoResults(results_returned, fullId, pos, -1, returned, 0, maxLevDistance);
+ addIntoResults(results_in_args, fullId, pos, -1, in_args.dist, 0, maxEditDistance);
+ addIntoResults(results_returned, fullId, pos, -1, returned.dist, 0, maxEditDistance);
- if (!typePassesFilter(parsedQuery.typeFilter, row.ty)) {
+ if (!typePassesFilter(elem.typeFilter, row.ty)) {
return;
}
@@ -1403,34 +1577,34 @@ function initSearch(rawSearchIndex) {
// No need to check anything else if it's a "pure" generics search.
if (elem.name.length === 0) {
if (row.type !== null) {
- lev = checkGenerics(row.type, elem, maxLevDistance + 1, maxLevDistance);
- // path_lev is 0 because we know it's empty
- addIntoResults(results_others, fullId, pos, index, lev, 0, maxLevDistance);
+ dist = checkGenerics(row.type, elem, maxEditDistance + 1, maxEditDistance);
+ // path_dist is 0 because we know it's empty
+ addIntoResults(results_others, fullId, pos, index, dist, 0, maxEditDistance);
}
return;
}
if (elem.fullPath.length > 1) {
- path_lev = checkPath(elem.pathWithoutLast, row, maxLevDistance);
- if (path_lev > maxLevDistance) {
+ path_dist = checkPath(elem.pathWithoutLast, row, maxEditDistance);
+ if (path_dist > maxEditDistance) {
return;
}
}
if (parsedQuery.literalSearch) {
if (searchWord === elem.name) {
- addIntoResults(results_others, fullId, pos, index, 0, path_lev);
+ addIntoResults(results_others, fullId, pos, index, 0, path_dist);
}
return;
}
- lev = levenshtein(searchWord, elem.pathLast);
+ dist = editDistance(searchWord, elem.pathLast, maxEditDistance);
- if (index === -1 && lev + path_lev > maxLevDistance) {
+ if (index === -1 && dist + path_dist > maxEditDistance) {
return;
}
- addIntoResults(results_others, fullId, pos, index, lev, path_lev, maxLevDistance);
+ addIntoResults(results_others, fullId, pos, index, dist, path_dist, maxEditDistance);
}
/**
@@ -1442,22 +1616,29 @@ function initSearch(rawSearchIndex) {
* @param {integer} pos - Position in the `searchIndex`.
* @param {Object} results
*/
- function handleArgs(row, pos, results, maxLevDistance) {
+ function handleArgs(row, pos, results, maxEditDistance) {
if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
return;
}
- let totalLev = 0;
- let nbLev = 0;
+ let totalDist = 0;
+ let nbDist = 0;
// If the result is too "bad", we return false and it ends this search.
function checkArgs(elems, callback) {
+ const skipPositions = [];
for (const elem of elems) {
// There is more than one parameter to the query so all checks should be "exact"
- const lev = callback(row, elem, NO_TYPE_FILTER, maxLevDistance);
- if (lev <= 1) {
- nbLev += 1;
- totalLev += lev;
+ const { dist, position } = callback(
+ row,
+ elem,
+ maxEditDistance,
+ skipPositions
+ );
+ if (dist <= 1) {
+ nbDist += 1;
+ totalDist += dist;
+ skipPositions.push(position);
} else {
return false;
}
@@ -1471,11 +1652,11 @@ function initSearch(rawSearchIndex) {
return;
}
- if (nbLev === 0) {
+ if (nbDist === 0) {
return;
}
- const lev = Math.round(totalLev / nbLev);
- addIntoResults(results, row.id, pos, 0, lev, 0, maxLevDistance);
+ const dist = Math.round(totalDist / nbDist);
+ addIntoResults(results, row.id, pos, 0, dist, 0, maxEditDistance);
}
function innerRunQuery() {
@@ -1488,7 +1669,7 @@ function initSearch(rawSearchIndex) {
for (const elem of parsedQuery.returned) {
queryLen += elem.name.length;
}
- const maxLevDistance = Math.floor(queryLen / 3);
+ const maxEditDistance = Math.floor(queryLen / 3);
if (parsedQuery.foundElems === 1) {
if (parsedQuery.elems.length === 1) {
@@ -1503,7 +1684,7 @@ function initSearch(rawSearchIndex) {
results_others,
results_in_args,
results_returned,
- maxLevDistance
+ maxEditDistance
);
}
} else if (parsedQuery.returned.length === 1) {
@@ -1514,15 +1695,22 @@ function initSearch(rawSearchIndex) {
in_returned = checkReturned(
row,
elem,
- parsedQuery.typeFilter,
- maxLevDistance
+ maxEditDistance,
+ []
+ );
+ addIntoResults(
+ results_others,
+ row.id,
+ i,
+ -1,
+ in_returned.dist,
+ maxEditDistance
);
- addIntoResults(results_others, row.id, i, -1, in_returned, maxLevDistance);
}
}
} else if (parsedQuery.foundElems > 0) {
for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
- handleArgs(searchIndex[i], i, results_others, maxLevDistance);
+ handleArgs(searchIndex[i], i, results_others, maxEditDistance);
}
}
}
@@ -1560,7 +1748,7 @@ function initSearch(rawSearchIndex) {
*
* @return {boolean} - Whether the result is valid or not
*/
- function validateResult(name, path, keys, parent, maxLevDistance) {
+ function validateResult(name, path, keys, parent, maxEditDistance) {
if (!keys || !keys.length) {
return true;
}
@@ -1574,8 +1762,8 @@ function initSearch(rawSearchIndex) {
// 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 a levenshtein match
- levenshtein(name, key) <= maxLevDistance)) {
+ // lastly check to see if the name was an editDistance match
+ editDistance(name, key, maxEditDistance) <= maxEditDistance)) {
return false;
}
}
@@ -1762,11 +1950,7 @@ function initSearch(rawSearchIndex) {
function showResults(results, go_to_first, filterCrates) {
const search = searchState.outputElement();
if (go_to_first || (results.others.length === 1
- && getSettingValue("go-to-only-result") === "true"
- // By default, the search DOM element is "empty" (meaning it has no children not
- // text content). Once a search has been run, it won't be empty, even if you press
- // ESC or empty the search input (which also "cancels" the search).
- && (!search.firstChild || search.firstChild.innerText !== searchState.loadingText))
+ && getSettingValue("go-to-only-result") === "true")
) {
const elem = document.createElement("a");
elem.href = results.others[0].href;
@@ -2064,10 +2248,11 @@ function initSearch(rawSearchIndex) {
* n: Array<string>,
* t: String,
* d: Array<string>,
- * q: Array<string>,
+ * q: Array<[Number, string]>,
* i: Array<Number>,
* f: Array<RawFunctionSearchType>,
* p: Array<Object>,
+ * c: Array<Number>
* }}
*/
const crateCorpus = rawSearchIndex[crate];
@@ -2086,6 +2271,7 @@ function initSearch(rawSearchIndex) {
type: null,
id: id,
normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
+ deprecated: null,
};
id += 1;
searchIndex.push(crateRow);
@@ -2095,14 +2281,20 @@ function initSearch(rawSearchIndex) {
const itemTypes = crateCorpus.t;
// an array of (String) item names
const itemNames = crateCorpus.n;
- // an array of (String) full paths (or empty string for previous path)
- const itemPaths = crateCorpus.q;
+ // an array of [(Number) item index,
+ // (String) full path]
+ // an item whose index is not present will fall back to the previous present path
+ // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present,
+ // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11
+ const itemPaths = new Map(crateCorpus.q);
// an array of (String) descriptions
const itemDescs = crateCorpus.d;
// an array of (Number) the parent path index + 1 to `paths`, or 0 if none
const itemParentIdxs = crateCorpus.i;
// an array of (Object | null) the type of the function, if any
const itemFunctionSearchTypes = crateCorpus.f;
+ // an array of (Number) indices for the deprecated items
+ const deprecatedItems = new Set(crateCorpus.c);
// an array of [(Number) item type,
// (String) name]
const paths = crateCorpus.p;
@@ -2142,12 +2334,13 @@ function initSearch(rawSearchIndex) {
crate: crate,
ty: itemTypes.charCodeAt(i) - charA,
name: itemNames[i],
- path: itemPaths[i] ? itemPaths[i] : lastPath,
+ path: itemPaths.has(i) ? itemPaths.get(i) : lastPath,
desc: itemDescs[i],
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
type: buildFunctionSearchType(itemFunctionSearchTypes[i], lowercasePaths),
id: id,
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
+ deprecated: deprecatedItems.has(i),
};
id += 1;
searchIndex.push(row);
@@ -2156,17 +2349,22 @@ function initSearch(rawSearchIndex) {
}
if (aliases) {
- ALIASES[crate] = Object.create(null);
+ const currentCrateAliases = new Map();
+ ALIASES.set(crate, currentCrateAliases);
for (const alias_name in aliases) {
if (!hasOwnPropertyRustdoc(aliases, alias_name)) {
continue;
}
- if (!hasOwnPropertyRustdoc(ALIASES[crate], alias_name)) {
- ALIASES[crate][alias_name] = [];
+ let currentNameAliases;
+ if (currentCrateAliases.has(alias_name)) {
+ currentNameAliases = currentCrateAliases.get(alias_name);
+ } else {
+ currentNameAliases = [];
+ currentCrateAliases.set(alias_name, currentNameAliases);
}
for (const local_alias of aliases[alias_name]) {
- ALIASES[crate][alias_name].push(local_alias + currentIndex);
+ currentNameAliases.push(local_alias + currentIndex);
}
}
}
diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js
index 1cd552e7f..ebbe6c1ca 100644
--- a/src/librustdoc/html/static/js/settings.js
+++ b/src/librustdoc/html/static/js/settings.js
@@ -86,12 +86,8 @@
if (settingId === "theme") {
const useSystem = getSettingValue("use-system-theme");
if (useSystem === "true" || settingValue === null) {
- if (useSystem !== "false") {
- settingValue = "system preference";
- } else {
- // This is the default theme.
- settingValue = "light";
- }
+ // "light" is the default theme
+ settingValue = useSystem === "false" ? "light" : "system preference";
}
}
if (settingValue !== null && settingValue !== "null") {
diff --git a/src/librustdoc/html/static/js/source-script.js b/src/librustdoc/html/static/js/source-script.js
index 6c0f03b5b..9aa755173 100644
--- a/src/librustdoc/html/static/js/source-script.js
+++ b/src/librustdoc/html/static/js/source-script.js
@@ -15,8 +15,13 @@ const NAME_OFFSET = 0;
const DIRS_OFFSET = 1;
const FILES_OFFSET = 2;
+// WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY
+// If you update this line, then you also need to update the media query with the same
+// warning in rustdoc.css
+const RUSTDOC_MOBILE_BREAKPOINT = 700;
+
function closeSidebarIfMobile() {
- if (window.innerWidth < window.RUSTDOC_MOBILE_BREAKPOINT) {
+ if (window.innerWidth < RUSTDOC_MOBILE_BREAKPOINT) {
updateLocalStorage("source-sidebar-show", "false");
}
}
@@ -69,12 +74,10 @@ function createDirEntry(elem, parent, fullPath, hasFoundFile) {
function toggleSidebar() {
const child = this.parentNode.children[0];
if (child.innerText === ">") {
- window.rustdocMobileScrollLock();
addClass(document.documentElement, "source-sidebar-expanded");
child.innerText = "<";
updateLocalStorage("source-sidebar-show", "true");
} else {
- window.rustdocMobileScrollUnlock();
removeClass(document.documentElement, "source-sidebar-expanded");
child.innerText = ">";
updateLocalStorage("source-sidebar-show", "false");
diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js
index c72ac254f..93979a944 100644
--- a/src/librustdoc/html/static/js/storage.js
+++ b/src/librustdoc/html/static/js/storage.js
@@ -7,31 +7,15 @@
const darkThemes = ["dark", "ayu"];
window.currentTheme = document.getElementById("themeStyle");
-window.mainTheme = document.getElementById("mainThemeStyle");
-
-// WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY
-// If you update this line, then you also need to update the media query with the same
-// warning in rustdoc.css
-window.RUSTDOC_MOBILE_BREAKPOINT = 700;
const settingsDataset = (function() {
const settingsElement = document.getElementById("default-settings");
- if (settingsElement === null) {
- return null;
- }
- const dataset = settingsElement.dataset;
- if (dataset === undefined) {
- return null;
- }
- return dataset;
+ return settingsElement && settingsElement.dataset ? settingsElement.dataset : null;
})();
function getSettingValue(settingName) {
const current = getCurrentValue(settingName);
- if (current !== null) {
- return current;
- }
- if (settingsDataset !== null) {
+ if (current === null && settingsDataset !== null) {
// See the comment for `default_settings.into_iter()` etc. in
// `Options::from_matches` in `librustdoc/config.rs`.
const def = settingsDataset[settingName.replace(/-/g,"_")];
@@ -39,31 +23,27 @@ function getSettingValue(settingName) {
return def;
}
}
- return null;
+ return current;
}
const localStoredTheme = getSettingValue("theme");
-const savedHref = [];
-
// eslint-disable-next-line no-unused-vars
function hasClass(elem, className) {
return elem && elem.classList && elem.classList.contains(className);
}
function addClass(elem, className) {
- if (!elem || !elem.classList) {
- return;
+ if (elem && elem.classList) {
+ elem.classList.add(className);
}
- elem.classList.add(className);
}
// eslint-disable-next-line no-unused-vars
function removeClass(elem, className) {
- if (!elem || !elem.classList) {
- return;
+ if (elem && elem.classList) {
+ elem.classList.remove(className);
}
- elem.classList.remove(className);
}
/**
@@ -73,10 +53,9 @@ function removeClass(elem, className) {
* @param {boolean} [reversed] - Whether to iterate in reverse
*/
function onEach(arr, func, reversed) {
- if (arr && arr.length > 0 && func) {
+ if (arr && arr.length > 0) {
if (reversed) {
- const length = arr.length;
- for (let i = length - 1; i >= 0; --i) {
+ for (let i = arr.length - 1; i >= 0; --i) {
if (func(arr[i])) {
return true;
}
@@ -102,6 +81,7 @@ function onEach(arr, func, reversed) {
* @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) {
return onEach(
Array.prototype.slice.call(lazyArray),
@@ -125,34 +105,40 @@ function getCurrentValue(name) {
}
}
-function switchTheme(styleElem, mainStyleElem, newThemeName, saveTheme) {
+// Get a value from the rustdoc-vars div, which is used to convey data from
+// Rust to the JS. If there is no such element, return null.
+const getVar = (function getVar(name) {
+ const el = document.getElementById("rustdoc-vars");
+ return el ? el.attributes["data-" + name].value : null;
+});
+
+function switchTheme(newThemeName, saveTheme) {
// If this new value comes from a system setting or from the previously
// saved theme, no need to save it.
if (saveTheme) {
updateLocalStorage("theme", newThemeName);
}
- if (savedHref.length === 0) {
- onEachLazy(document.getElementsByTagName("link"), el => {
- savedHref.push(el.href);
- });
+ let newHref;
+
+ if (newThemeName === "light" || newThemeName === "dark" || newThemeName === "ayu") {
+ newHref = getVar("static-root-path") + getVar("theme-" + newThemeName + "-css");
+ } else {
+ newHref = getVar("root-path") + newThemeName + getVar("resource-suffix") + ".css";
}
- const newHref = savedHref.find(url => {
- const m = url.match(/static\.files\/(.*)-[a-f0-9]{16}\.css$/);
- if (m && m[1] === newThemeName) {
- return true;
- }
- const m2 = url.match(/\/([^/]*)\.css$/);
- if (m2 && m2[1].startsWith(newThemeName)) {
- return true;
- }
- });
- if (newHref && newHref !== styleElem.href) {
- styleElem.href = newHref;
+
+ if (!window.currentTheme) {
+ document.write(`<link rel="stylesheet" id="themeStyle" href="${newHref}">`);
+ window.currentTheme = document.getElementById("themeStyle");
+ } else if (newHref !== window.currentTheme.href) {
+ window.currentTheme.href = newHref;
}
}
const updateTheme = (function() {
+ // only listen to (prefers-color-scheme: dark) because light is the default
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
+
/**
* Update the current theme to match whatever the current combination of
* * the preference for using the system theme
@@ -163,60 +149,23 @@ const updateTheme = (function() {
* … dictates that it should be.
*/
function updateTheme() {
- const use = (theme, saveTheme) => {
- switchTheme(window.currentTheme, window.mainTheme, theme, saveTheme);
- };
-
// maybe the user has disabled the setting in the meantime!
if (getSettingValue("use-system-theme") !== "false") {
const lightTheme = getSettingValue("preferred-light-theme") || "light";
const darkTheme = getSettingValue("preferred-dark-theme") || "dark";
+ updateLocalStorage("use-system-theme", "true");
- if (isDarkMode()) {
- use(darkTheme, true);
- } else {
- // prefers a light theme, or has no preference
- use(lightTheme, true);
- }
+ // use light theme if user prefers it, or has no preference
+ switchTheme(mql.matches ? darkTheme : lightTheme, true);
// note: we save the theme so that it doesn't suddenly change when
// the user disables "use-system-theme" and reloads the page or
// navigates to another page
} else {
- use(getSettingValue("theme"), false);
+ switchTheme(getSettingValue("theme"), false);
}
}
- // This is always updated below to a function () => bool.
- let isDarkMode;
-
- // Determine the function for isDarkMode, and if we have
- // `window.matchMedia`, set up an event listener on the preferred color
- // scheme.
- //
- // Otherwise, fall back to the prefers-color-scheme value CSS captured in
- // the "content" property.
- if (window.matchMedia) {
- // only listen to (prefers-color-scheme: dark) because light is the default
- const mql = window.matchMedia("(prefers-color-scheme: dark)");
-
- isDarkMode = () => mql.matches;
-
- if (mql.addEventListener) {
- mql.addEventListener("change", updateTheme);
- } else {
- // This is deprecated, see:
- // https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener
- mql.addListener(updateTheme);
- }
- } else {
- // fallback to the CSS computed value
- const cssContent = getComputedStyle(document.documentElement)
- .getPropertyValue("content");
- // (Note: the double-quotes come from that this is a CSS value, which
- // might be a length, string, etc.)
- const cssColorScheme = cssContent || "\"light\"";
- isDarkMode = () => (cssColorScheme === "\"dark\"");
- }
+ mql.addEventListener("change", updateTheme);
return updateTheme;
})();
diff --git a/src/librustdoc/html/templates/STYLE.md b/src/librustdoc/html/templates/STYLE.md
index fff65e3b5..0281b1c47 100644
--- a/src/librustdoc/html/templates/STYLE.md
+++ b/src/librustdoc/html/templates/STYLE.md
@@ -1,37 +1,38 @@
# Style for Templates
-This directory has templates in the [Tera templating language](teradoc), which is very
-similar to [Jinja2](jinjadoc) and [Django](djangodoc) templates, and also to [Askama](askamadoc).
+This directory has templates in the [Tera templating language][teradoc], which is very
+similar to [Jinja2][jinjadoc] and [Django][djangodoc] templates, and also to [Askama][askamadoc].
[teradoc]: https://tera.netlify.app/docs/#templates
-[jinjadoc]: https://jinja.palletsprojects.com/en/3.0.x/templates/
-[djangodoc]: https://docs.djangoproject.com/en/3.2/topics/templates/
-[askamadoc]: https://docs.rs/askama/0.10.5/askama/
+[jinjadoc]: https://jinja.palletsprojects.com/en/3.1.x/templates/
+[djangodoc]: https://docs.djangoproject.com/en/4.1/topics/templates/
+[askamadoc]: https://docs.rs/askama/latest/askama/
We want our rendered output to have as little unnecessary whitespace as
possible, so that pages load quickly. To achieve that we use Tera's
-[whitespace control] features. At the end of most lines, we put an empty comment
-tag with the whitespace control characters: `{#- -#}`. This causes all
-whitespace between the end of the line and the beginning of the next, including
-indentation, to be omitted on render. Sometimes we want to preserve a single
-space. In those cases we put the space at the end of the line, followed by
-`{# -#}`, which is a directive to remove following whitespace but not preceding.
-We also use the whitespace control characters in most instances of tags with
-control flow, for example `{%- if foo -%}`.
+[whitespace control] features. By default, whitespace characters are removed
+around jinja tags (`{% %}` for example). At the end of most lines, we put an
+empty comment tag: `{# #}`. This causes all whitespace between the end of the
+line and the beginning of the next, including indentation, to be omitted on
+render. Sometimes we want to preserve a single space. In those cases we put the
+space at the end of the line, followed by `{#+ #}`, which is a directive to
+remove following whitespace but not preceding. We also use the whitespace
+control characters in most instances of tags with control flow, for example
+`{% if foo %}`.
[whitespace control]: https://tera.netlify.app/docs/#whitespace-control
We want our templates to be readable, so we use indentation and newlines
-liberally. We indent by four spaces after opening an HTML tag _or_ a Tera
+liberally. We indent by four spaces after opening an HTML tag _or_ a Jinja
tag. In most cases an HTML tag should be followed by a newline, but if the
tag has simple contents and fits with its close tag on a single line, the
contents don't necessarily need a new line.
-Tera templates support quite sophisticated control flow. To keep our templates
+Askama templates support quite sophisticated control flow. To keep our templates
simple and understandable, we use only a subset: `if` and `for`. In particular
-we avoid [assignments in the template logic](assignments) and [Tera
-macros](macros). This also may make things easier if we switch to a different
+we avoid [assignments in the template logic][assignments] and [Askama
+macros][macros]. This also may make things easier if we switch to a different
Jinja-style template system, like Askama, in the future.
-[assignments]: https://tera.netlify.app/docs/#assignments
-[macros]: https://tera.netlify.app/docs/#macros
+[assignments]: https://djc.github.io/askama/template_syntax.html#assignments
+[macros]: https://djc.github.io/askama/template_syntax.html#macros
diff --git a/src/librustdoc/html/templates/item_info.html b/src/librustdoc/html/templates/item_info.html
new file mode 100644
index 000000000..d2ea9bdae
--- /dev/null
+++ b/src/librustdoc/html/templates/item_info.html
@@ -0,0 +1,7 @@
+{% if !items.is_empty() %}
+ <span class="item-info"> {# #}
+ {% for item in items %}
+ {{item|safe}} {# #}
+ {% endfor %}
+ </span>
+{% endif %}
diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html
new file mode 100644
index 000000000..a01457971
--- /dev/null
+++ b/src/librustdoc/html/templates/item_union.html
@@ -0,0 +1,23 @@
+<pre class="rust item-decl"><code>
+ {{ self.render_attributes_in_pre() | safe }}
+ {{ self.render_union() | safe }}
+</code></pre>
+{{ self.document() | safe }}
+{% if self.fields_iter().peek().is_some() %}
+ <h2 id="fields" class="fields small-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">
+ <a href="#structfield.{{ name }}" class="anchor field">§</a>
+ <code>{{ name }}: {{ self.print_ty(ty) | safe }}</code>
+ </span>
+ {% if let Some(stability_class) = self.stability_field(field) %}
+ <span class="stab {{ stability_class }}"></span>
+ {% endif %}
+ {{ self.document_field(field) | safe }}
+ {% endfor %}
+{% endif %}
+{{ self.render_assoc_items() | safe }}
+{{ self.document_type_layout() | safe }}
diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html
index 7690d8f25..9133f899a 100644
--- a/src/librustdoc/html/templates/page.html
+++ b/src/librustdoc/html/templates/page.html
@@ -1,148 +1,151 @@
-<!DOCTYPE html> {#- -#}
-<html lang="en"> {#- -#}
-<head> {#- -#}
- <meta charset="utf-8"> {#- -#}
- <meta name="viewport" content="width=device-width, initial-scale=1.0"> {#- -#}
- <meta name="generator" content="rustdoc"> {#- -#}
- <meta name="description" content="{{page.description}}"> {#- -#}
- <title>{{page.title}}</title> {#- -#}
- <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_serif_4_regular}}"> {#- -#}
- <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.fira_sans_regular}}"> {#- -#}
- <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.fira_sans_medium}}"> {#- -#}
- <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_code_pro_regular}}"> {#- -#}
- <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_serif_4_bold}}"> {#- -#}
- <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_code_pro_semibold}}"> {#- -#}
- <link rel="stylesheet" {# -#}
- href="{{static_root_path|safe}}{{files.normalize_css}}"> {#- -#}
- <link rel="stylesheet" {# -#}
- href="{{static_root_path|safe}}{{files.rustdoc_css}}" {# -#}
- id="mainThemeStyle"> {#- -#}
- <link rel="stylesheet" id="themeStyle" href="{{static_root_path|safe}}{{files.theme_light_css}}"> {#- -#}
- <link rel="stylesheet" disabled href="{{static_root_path|safe}}{{files.theme_dark_css}}"> {#- -#}
- <link rel="stylesheet" disabled href="{{static_root_path|safe}}{{files.theme_ayu_css}}"> {#- -#}
- {%- for theme in themes -%}
- <link rel="stylesheet" disabled href="{{page.root_path|safe}}{{theme}}{{page.resource_suffix}}.css"> {#- -#}
- {%- endfor -%}
- {%- if !layout.default_settings.is_empty() -%}
- <script id="default-settings" {# -#}
- {% for (k, v) in layout.default_settings %}
+<!DOCTYPE html> {# #}
+<html lang="en"> {# #}
+<head> {# #}
+ <meta charset="utf-8"> {# #}
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"> {# #}
+ <meta name="generator" content="rustdoc"> {# #}
+ <meta name="description" content="{{page.description}}"> {# #}
+ <title>{{page.title}}</title> {# #}
+ <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_serif_4_regular}}"> {# #}
+ <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.fira_sans_regular}}"> {# #}
+ <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.fira_sans_medium}}"> {# #}
+ <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_code_pro_regular}}"> {# #}
+ <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_serif_4_bold}}"> {# #}
+ <link rel="preload" as="font" type="font/woff2" crossorigin href="{{static_root_path|safe}}{{files.source_code_pro_semibold}}"> {# #}
+ <link rel="stylesheet" {#+ #}
+ href="{{static_root_path|safe}}{{files.normalize_css}}"> {# #}
+ <link rel="stylesheet" {#+ #}
+ href="{{static_root_path|safe}}{{files.rustdoc_css}}" {#+ #}
+ id="mainThemeStyle"> {# #}
+ {% if !layout.default_settings.is_empty() %}
+ <script id="default-settings" {#+ #}
+ {%~ for (k, v) in layout.default_settings ~%}
data-{{k}}="{{v}}"
- {%- endfor -%}
- ></script> {#- -#}
- {%- endif -%}
- <script src="{{static_root_path|safe}}{{files.storage_js}}"></script> {#- -#}
- {%- if page.css_class.contains("crate") -%}
- <script defer src="{{page.root_path|safe}}crates{{page.resource_suffix}}.js"></script> {#- -#}
- {%- else if page.css_class == "source" -%}
- <script defer src="{{static_root_path|safe}}{{files.source_script_js}}"></script> {#- -#}
- <script defer src="{{page.root_path|safe}}source-files{{page.resource_suffix}}.js"></script> {#- -#}
- {%- else if !page.css_class.contains("mod") -%}
- <script defer src="sidebar-items{{page.resource_suffix}}.js"></script> {#- -#}
- {%- endif -%}
- <script defer src="{{static_root_path|safe}}{{files.main_js}}"></script> {#- -#}
- {%- if layout.scrape_examples_extension -%}
- <script defer src="{{static_root_path|safe}}{{files.scrape_examples_js}}"></script> {#- -#}
- {%- endif -%}
- <noscript> {#- -#}
- <link rel="stylesheet" {# -#}
- href="{{static_root_path|safe}}{{files.noscript_css}}"> {#- -#}
- </noscript> {#- -#}
- {%- if layout.css_file_extension.is_some() -%}
- <link rel="stylesheet" {# -#}
- href="{{static_root_path|safe}}theme{{page.resource_suffix}}.css"> {#- -#}
- {%- endif -%}
- {%- if !layout.favicon.is_empty() -%}
- <link rel="icon" href="{{layout.favicon}}"> {#- -#}
- {%- else -%}
- <link rel="alternate icon" type="image/png" {# -#}
- href="{{static_root_path|safe}}{{files.rust_favicon_png_16}}"> {#- -#}
- <link rel="alternate icon" type="image/png" {# -#}
- href="{{static_root_path|safe}}{{files.rust_favicon_png_32}}"> {#- -#}
- <link rel="icon" type="image/svg+xml" {# -#}
- href="{{static_root_path|safe}}{{files.rust_favicon_svg}}"> {#- -#}
- {%- endif -%}
- {{- layout.external_html.in_header|safe -}}
-</head> {#- -#}
-<body class="rustdoc {{page.css_class}}"> {#- -#}
- <!--[if lte IE 11]> {#- -#}
- <div class="warning"> {#- -#}
- This old browser is unsupported and will most likely display funky things. {#- -#}
- </div> {#- -#}
- <![endif]--> {#- -#}
- {{- layout.external_html.before_content|safe -}}
- {%- if page.css_class != "source" -%}
- <nav class="mobile-topbar"> {#- -#}
- <button class="sidebar-menu-toggle">&#9776;</button> {#- -#}
- <a class="logo-container" href="{{page.root_path|safe}}{{krate_with_trailing_slash|safe}}index.html"> {#- -#}
- {%- if !layout.logo.is_empty() -%}
- <img src="{{layout.logo}}" alt="logo"> {#- -#}
- {%- else -%}
- <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="logo"> {#- -#}
- {%- endif -%}
- </a> {#- -#}
- <h2></h2> {#- -#}
- </nav> {#- -#}
- {%- endif -%}
- <nav class="sidebar"> {#- -#}
- {%- if page.css_class != "source" -%}
- <a class="logo-container" href="{{page.root_path|safe}}{{krate_with_trailing_slash|safe}}index.html"> {#- -#}
- {%- if !layout.logo.is_empty() %}
- <img src="{{layout.logo}}" alt="logo"> {#- -#}
- {%- else -%}
- <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="logo"> {#- -#}
- {%- endif -%}
- </a> {#- -#}
- {%- endif -%}
- {{- sidebar|safe -}}
- </nav> {#- -#}
- <main> {#- -#}
- {%- if page.css_class != "source" -%}<div class="width-limiter">{%- endif -%}
- <nav class="sub"> {#- -#}
- {%- if page.css_class == "source" -%}
- <a class="sub-logo-container" href="{{page.root_path|safe}}{{krate_with_trailing_slash|safe}}index.html"> {#- -#}
- {%- if !layout.logo.is_empty() %}
- <img src="{{layout.logo}}" alt="logo"> {#- -#}
- {%- else -%}
- <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="logo"> {#- -#}
- {%- endif -%}
- </a> {#- -#}
- {%- endif -%}
- <form class="search-form"> {#- -#}
- <span></span> {#- This empty span is a hacky fix for Safari - See #93184 -#}
- <input {# -#}
- class="search-input" {# -#}
- name="search" {# -#}
- aria-label="Run search in the documentation" {# -#}
- autocomplete="off" {# -#}
- 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> {#- -#}
- <div id="settings-menu" tabindex="-1"> {#- -#}
- <a href="{{page.root_path|safe}}settings.html" title="settings"> {#- -#}
- <img width="22" height="22" alt="Change settings" {# -#}
- src="{{static_root_path|safe}}{{files.wheel_svg}}"> {#- -#}
- </a> {#- -#}
- </div> {#- -#}
- </form> {#- -#}
- </nav> {#- -#}
- <section id="main-content" class="content">{{- content|safe -}}</section> {#- -#}
- {%- if page.css_class != "source" -%}</div>{%- endif -%}
- </main> {#- -#}
- {{- layout.external_html.after_content|safe -}}
- <div id="rustdoc-vars" {# -#}
- data-root-path="{{page.root_path|safe}}" {# -#}
- data-static-root-path="{{static_root_path|safe}}" {# -#}
- data-current-crate="{{layout.krate}}" {# -#}
- data-themes="{{themes|join(",") }}" {# -#}
- data-resource-suffix="{{page.resource_suffix}}" {# -#}
- data-rustdoc-version="{{rustdoc_version}}" {# -#}
- data-search-js="{{files.search_js}}" {# -#}
- data-settings-js="{{files.settings_js}}" {# -#}
- data-settings-css="{{files.settings_css}}" {# -#}
- > {#- -#}
- </div> {#- -#}
-</body> {#- -#}
-</html> {#- -#}
+ {% endfor %}
+ ></script> {# #}
+ {% endif %}
+ <div id="rustdoc-vars" {#+ #}
+ data-root-path="{{page.root_path|safe}}" {#+ #}
+ data-static-root-path="{{static_root_path|safe}}" {#+ #}
+ data-current-crate="{{layout.krate}}" {#+ #}
+ data-themes="{{themes|join(",") }}" {#+ #}
+ data-resource-suffix="{{page.resource_suffix}}" {#+ #}
+ data-rustdoc-version="{{rustdoc_version}}" {#+ #}
+ data-search-js="{{files.search_js}}" {#+ #}
+ data-settings-js="{{files.settings_js}}" {#+ #}
+ data-settings-css="{{files.settings_css}}" {#+ #}
+ data-theme-light-css="{{files.theme_light_css}}" {#+ #}
+ data-theme-dark-css="{{files.theme_dark_css}}" {#+ #}
+ data-theme-ayu-css="{{files.theme_ayu_css}}" {#+ #}
+ > {# #}
+ </div> {# #}
+ <script src="{{static_root_path|safe}}{{files.storage_js}}"></script> {# #}
+ {% if page.css_class.contains("crate") %}
+ <script defer src="{{page.root_path|safe}}crates{{page.resource_suffix}}.js"></script> {# #}
+ {% else if page.css_class == "source" %}
+ <script defer src="{{static_root_path|safe}}{{files.source_script_js}}"></script> {# #}
+ <script defer src="{{page.root_path|safe}}source-files{{page.resource_suffix}}.js"></script> {# #}
+ {% else if !page.css_class.contains("mod") %}
+ <script defer src="sidebar-items{{page.resource_suffix}}.js"></script> {# #}
+ {% endif %}
+ <script defer src="{{static_root_path|safe}}{{files.main_js}}"></script> {# #}
+ {% if layout.scrape_examples_extension %}
+ <script defer src="{{static_root_path|safe}}{{files.scrape_examples_js}}"></script> {# #}
+ {% endif %}
+ <noscript> {# #}
+ <link rel="stylesheet" {#+ #}
+ media="(prefers-color-scheme:light)" {#+ #}
+ href="{{static_root_path|safe}}{{files.theme_light_css}}"> {# #}
+ <link rel="stylesheet" {#+ #}
+ media="(prefers-color-scheme:dark)" {#+ #}
+ href="{{static_root_path|safe}}{{files.theme_dark_css}}"> {# #}
+ <link rel="stylesheet" {#+ #}
+ href="{{static_root_path|safe}}{{files.noscript_css}}"> {# #}
+ </noscript> {# #}
+ {% if layout.css_file_extension.is_some() %}
+ <link rel="stylesheet" {#+ #}
+ href="{{page.root_path|safe}}theme{{page.resource_suffix}}.css"> {# #}
+ {% endif %}
+ {% if !layout.favicon.is_empty() %}
+ <link rel="icon" href="{{layout.favicon}}"> {# #}
+ {% else %}
+ <link rel="alternate icon" type="image/png" {#+ #}
+ href="{{static_root_path|safe}}{{files.rust_favicon_png_16}}"> {# #}
+ <link rel="alternate icon" type="image/png" {#+ #}
+ href="{{static_root_path|safe}}{{files.rust_favicon_png_32}}"> {# #}
+ <link rel="icon" type="image/svg+xml" {#+ #}
+ href="{{static_root_path|safe}}{{files.rust_favicon_svg}}"> {# #}
+ {% endif %}
+ {{ layout.external_html.in_header|safe }}
+</head> {# #}
+<body class="rustdoc {{+page.css_class}}"> {# #}
+ <!--[if lte IE 11]> {# #}
+ <div class="warning"> {# #}
+ This old browser is unsupported and will most likely display funky things. {# #}
+ </div> {# #}
+ <![endif]--> {# #}
+ {{ layout.external_html.before_content|safe }}
+ {% if page.css_class != "source" %}
+ <nav class="mobile-topbar"> {# #}
+ <button class="sidebar-menu-toggle">&#9776;</button> {# #}
+ <a class="logo-container" href="{{page.root_path|safe}}{{krate_with_trailing_slash|safe}}index.html"> {# #}
+ {% if !layout.logo.is_empty() %}
+ <img src="{{layout.logo}}" alt="logo"> {# #}
+ {% else %}
+ <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="logo"> {# #}
+ {% endif %}
+ </a> {# #}
+ <h2></h2> {# #}
+ </nav> {# #}
+ {% endif %}
+ <nav class="sidebar"> {# #}
+ {% if page.css_class != "source" %}
+ <a class="logo-container" href="{{page.root_path|safe}}{{krate_with_trailing_slash|safe}}index.html"> {# #}
+ {% if !layout.logo.is_empty() %}
+ <img src="{{layout.logo}}" alt="logo"> {# #}
+ {% else %}
+ <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="logo"> {# #}
+ {% endif %}
+ </a> {# #}
+ {% endif %}
+ {{ sidebar|safe }}
+ </nav> {# #}
+ <main> {# #}
+ {% if page.css_class != "source" %}<div class="width-limiter">{% endif %}
+ <nav class="sub"> {# #}
+ {% if page.css_class == "source" %}
+ <a class="sub-logo-container" href="{{page.root_path|safe}}{{krate_with_trailing_slash|safe}}index.html"> {# #}
+ {% if !layout.logo.is_empty() %}
+ <img src="{{layout.logo}}" alt="logo"> {# #}
+ {% else %}
+ <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="logo"> {# #}
+ {% endif %}
+ </a> {# #}
+ {% endif %}
+ <form class="search-form"> {# #}
+ <span></span> {# This empty span is a hacky fix for Safari - See #93184 #}
+ <input {#+ #}
+ class="search-input" {#+ #}
+ name="search" {#+ #}
+ aria-label="Run search in the documentation" {#+ #}
+ autocomplete="off" {#+ #}
+ 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> {# #}
+ <div id="settings-menu" tabindex="-1"> {# #}
+ <a href="{{page.root_path|safe}}settings.html" title="settings"> {# #}
+ <img width="22" height="22" alt="Change settings" {#+ #}
+ src="{{static_root_path|safe}}{{files.wheel_svg}}"> {# #}
+ </a> {# #}
+ </div> {# #}
+ </form> {# #}
+ </nav> {# #}
+ <section id="main-content" class="content">{{ content|safe }}</section> {# #}
+ {% if page.css_class != "source" %}</div>{% endif %}
+ </main> {# #}
+ {{ layout.external_html.after_content|safe }}
+</body> {# #}
+</html> {# #}
diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html
index 3a1867b7f..edabac9a0 100644
--- a/src/librustdoc/html/templates/print_item.html
+++ b/src/librustdoc/html/templates/print_item.html
@@ -1,28 +1,28 @@
-<div class="main-heading"> {#- -#}
- <h1> {#- -#}
- {{-typ-}}
- {#- The breadcrumbs of the item path, like std::string -#}
- {%- for component in path_components -%}
- <a href="{{component.path|safe}}index.html">{{component.name}}</a>::<wbr>
- {%- endfor -%}
- <a class="{{item_type}}" href="#">{{name}}</a> {#- -#}
- <button id="copy-path" title="Copy item path to clipboard"> {#- -#}
- <img src="{{static_root_path|safe}}{{clipboard_svg}}" {# -#}
- width="19" height="18" {# -#}
- alt="Copy item path"> {#- -#}
- </button> {#- -#}
- </h1> {#- -#}
- <span class="out-of-band"> {#- -#}
+<div class="main-heading"> {# #}
+ <h1> {# #}
+ {{typ}}
+ {# The breadcrumbs of the item path, like std::string #}
+ {% for component in path_components %}
+ <a href="{{component.path|safe}}index.html">{{component.name}}</a>::<wbr>
+ {% endfor %}
+ <a class="{{item_type}}" href="#">{{name}}</a> {# #}
+ <button id="copy-path" title="Copy item path to clipboard"> {# #}
+ <img src="{{static_root_path|safe}}{{clipboard_svg}}" {#+ #}
+ width="19" height="18" {#+ #}
+ alt="Copy item path"> {# #}
+ </button> {# #}
+ </h1> {# #}
+ <span class="out-of-band"> {# #}
{% if !stability_since_raw.is_empty() %}
- {{- stability_since_raw|safe }} · {# -#}
+ {{ stability_since_raw|safe +}} · {#+ #}
{% endif %}
- {%- match src_href -%}
- {%- when Some with (href) -%}
- <a class="srclink" href="{{href|safe}}">source</a> · {# -#}
- {%- else -%}
- {%- endmatch -%}
- <button id="toggle-all-docs" title="collapse all docs"> {#- -#}
- [<span>&#x2212;</span>] {#- -#}
- </button> {#- -#}
- </span> {#- -#}
-</div> {#- -#}
+ {% match src_href %}
+ {% when Some with (href) %}
+ <a class="srclink" href="{{href|safe}}">source</a> · {#+ #}
+ {% else %}
+ {% endmatch %}
+ <button id="toggle-all-docs" title="collapse all docs"> {# #}
+ [<span>&#x2212;</span>] {# #}
+ </button> {# #}
+ </span> {# #}
+</div> {# #}
diff --git a/src/librustdoc/html/templates/short_item_info.html b/src/librustdoc/html/templates/short_item_info.html
new file mode 100644
index 000000000..75d155e91
--- /dev/null
+++ b/src/librustdoc/html/templates/short_item_info.html
@@ -0,0 +1,23 @@
+{% match self %}
+ {% when Self::Deprecation with { message } %}
+ <div class="stab deprecated"> {# #}
+ <span class="emoji">👎</span> {# #}
+ <span>{{message|safe}}</span> {# #}
+ </div> {# #}
+ {% when Self::Unstable with { feature, tracking } %}
+ <div class="stab unstable"> {# #}
+ <span class="emoji">🔬</span> {# #}
+ <span> {# #}
+ This is a nightly-only experimental API. ({# #}
+ <code>{{feature}}</code> {# #}
+ {% match tracking %}
+ {% when Some with ((url, num)) %}
+ &nbsp;<a href="{{url}}{{num}}">#{{num}}</a> {# #}
+ {% when None %}
+ {% endmatch %}
+ ) {# #}
+ </span> {# #}
+ </div> {# #}
+ {% when Self::Portability with { message } %}
+ <div class="stab portability">{{message|safe}}</div> {# #}
+{% endmatch %}
diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html
new file mode 100644
index 000000000..01d476ad2
--- /dev/null
+++ b/src/librustdoc/html/templates/sidebar.html
@@ -0,0 +1,37 @@
+{% if !title.is_empty() %}
+ <h2 class="location"> {# #}
+ <a href="#">{{title_prefix}}{{title}}</a> {# #}
+ </h2>
+{% endif %}
+<div class="sidebar-elems">
+ {% if is_crate %}
+ <ul class="block">
+ {% if !version.is_empty() %}
+ <li class="version">Version {{+ version}}</li>
+ {% endif %}
+ <li><a id="all-types" href="all.html">All Items</a></li> {# #}
+ </ul>
+ {% endif %}
+
+ {% if self.should_render_blocks() %}
+ <section>
+ {% for block in blocks %}
+ {% if block.should_render() %}
+ {% if !block.heading.name.is_empty() %}
+ <h3><a href="#{{block.heading.href|safe}}">{{block.heading.name}}</a></h3>
+ {% endif %}
+ {% if !block.links.is_empty() %}
+ <ul class="block">
+ {% for link in block.links %}
+ <li><a href="#{{link.href|safe}}">{{link.name}}</a></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ {% endif %}
+ {% endfor %}
+ </section>
+ {% endif %}
+ {% if !path.is_empty() %}
+ <h2><a href="index.html">In {{+ path}}</a></h2>
+ {% endif %}
+</div>
diff --git a/src/librustdoc/html/templates/source.html b/src/librustdoc/html/templates/source.html
new file mode 100644
index 000000000..42d01277d
--- /dev/null
+++ b/src/librustdoc/html/templates/source.html
@@ -0,0 +1,21 @@
+<div class="example-wrap"> {# #}
+ {# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
+ Do not show "1 2 3 4 5 ..." in web search results. #}
+ <div data-nosnippet><pre class="src-line-numbers">
+ {% for line in lines.clone() %}
+ {% if embedded %}
+ <span>{{line|safe}}</span>
+ {%~ else %}
+ <a href="#{{line|safe}}" id="{{line|safe}}">{{line|safe}}</a>
+ {%~ endif %}
+ {% endfor %}
+ </pre></div> {# #}
+ <pre class="rust"> {# #}
+ <code>
+ {% if needs_expansion %}
+ <button class="expand">&varr;</button>
+ {% endif %}
+ {{code_html|safe}}
+ </code> {# #}
+ </pre> {# #}
+</div>
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 18c45fd69..cd6509607 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -4,7 +4,6 @@
#![allow(rustc::default_hash_types)]
-use std::convert::From;
use std::fmt;
use rustc_ast::ast;
@@ -249,9 +248,7 @@ pub(crate) fn id_from_item_inner(
// instead, we directly get the primitive symbol and convert it to u32 to
// generate the ID.
if matches!(tcx.def_kind(def_id), DefKind::Mod) &&
- let Some(prim) = tcx.get_attrs(*def_id, sym::doc)
- .flat_map(|attr| attr.meta_item_list().unwrap_or_default())
- .filter(|attr| attr.has_name(sym::primitive))
+ let Some(prim) = tcx.get_attrs(*def_id, sym::rustc_doc_primitive)
.find_map(|attr| attr.value_str()) {
format!(":{}", prim.as_u32())
} else {
@@ -456,7 +453,7 @@ impl FromWithTcx<clean::GenericParamDefKind> for GenericParamDefKind {
default: default.map(|x| (*x).into_tcx(tcx)),
synthetic,
},
- Const { did: _, ty, default } => GenericParamDefKind::Const {
+ Const { ty, default } => GenericParamDefKind::Const {
type_: (*ty).into_tcx(tcx),
default: default.map(|x| *x),
},
@@ -473,9 +470,35 @@ impl FromWithTcx<clean::WherePredicate> for WherePredicate {
bounds: bounds.into_tcx(tcx),
generic_params: bound_params
.into_iter()
- .map(|x| GenericParamDef {
- name: x.0.to_string(),
- kind: GenericParamDefKind::Lifetime { outlives: vec![] },
+ .map(|x| {
+ let name = x.name.to_string();
+ let kind = match x.kind {
+ clean::GenericParamDefKind::Lifetime { outlives } => {
+ GenericParamDefKind::Lifetime {
+ outlives: outlives.iter().map(|lt| lt.0.to_string()).collect(),
+ }
+ }
+ clean::GenericParamDefKind::Type {
+ did: _,
+ bounds,
+ default,
+ synthetic,
+ } => GenericParamDefKind::Type {
+ bounds: bounds
+ .into_iter()
+ .map(|bound| bound.into_tcx(tcx))
+ .collect(),
+ default: default.map(|ty| (*ty).into_tcx(tcx)),
+ synthetic,
+ },
+ clean::GenericParamDefKind::Const { ty, default } => {
+ GenericParamDefKind::Const {
+ type_: (*ty).into_tcx(tcx),
+ default: default.map(|d| *d),
+ }
+ }
+ };
+ GenericParamDef { name, kind }
})
.collect(),
},
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index 08bceb59c..d6da6e099 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -78,7 +78,7 @@ impl<'tcx> JsonRenderer<'tcx> {
// HACK(hkmatsumoto): For impls of primitive types, we index them
// regardless of whether they're local. This is because users can
// document primitive items in an arbitrary crate by using
- // `doc(primitive)`.
+ // `rustc_doc_primitive`.
let mut is_primitive_impl = false;
if let clean::types::ItemKind::ImplItem(ref impl_) = *item.kind &&
impl_.trait_.is_none() &&
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 4fcf08736..4a88dc525 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -7,14 +7,14 @@
#![feature(assert_matches)]
#![feature(box_patterns)]
#![feature(drain_filter)]
-#![feature(is_terminal)]
#![feature(let_chains)]
#![feature(test)]
#![feature(never_type)]
-#![feature(once_cell)]
+#![feature(lazy_cell)]
#![feature(type_ascription)]
#![feature(iter_intersperse)]
#![feature(type_alias_impl_trait)]
+#![cfg_attr(not(bootstrap), feature(impl_trait_in_assoc_type))]
#![recursion_limit = "256"]
#![warn(rustc::internal)]
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
@@ -69,7 +69,6 @@ extern crate test;
#[cfg(feature = "jemalloc")]
extern crate jemalloc_sys;
-use std::default::Default;
use std::env::{self, VarError};
use std::io::{self, IsTerminal};
use std::process;
@@ -203,7 +202,7 @@ fn init_logging() {
.with_verbose_exit(true)
.with_verbose_entry(true)
.with_indent_amount(2);
- #[cfg(parallel_compiler)]
+ #[cfg(all(parallel_compiler, debug_assertions))]
let layer = layer.with_thread_ids(true).with_thread_names(true);
use tracing_subscriber::layer::SubscriberExt;
@@ -284,7 +283,7 @@ fn opts() -> Vec<RustcOptGroup> {
stable("test-args", |o| {
o.optmulti("", "test-args", "arguments to pass to the test runner", "ARGS")
}),
- unstable("test-run-directory", |o| {
+ stable("test-run-directory", |o| {
o.optopt(
"",
"test-run-directory",
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs
index 0b22f943d..be5286b24 100644
--- a/src/librustdoc/passes/calculate_doc_coverage.rs
+++ b/src/librustdoc/passes/calculate_doc_coverage.rs
@@ -8,7 +8,6 @@ use crate::visit::DocVisitor;
use rustc_hir as hir;
use rustc_lint::builtin::MISSING_DOCS;
use rustc_middle::lint::LintLevelSource;
-use rustc_middle::ty::DefIdTree;
use rustc_session::lint;
use rustc_span::FileName;
use serde::Serialize;
diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs
index a39d57d42..6b13e6c95 100644
--- a/src/librustdoc/passes/check_doc_test_visibility.rs
+++ b/src/librustdoc/passes/check_doc_test_visibility.rs
@@ -14,7 +14,6 @@ use crate::visit::DocVisitor;
use crate::visit_ast::inherits_doc_hidden;
use rustc_hir as hir;
use rustc_middle::lint::LintLevelSource;
-use rustc_middle::ty::DefIdTree;
use rustc_session::lint;
pub(crate) const CHECK_DOC_TEST_VISIBILITY: Pass = Pass {
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index cbfc58138..2cd9c8a87 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -13,10 +13,10 @@ use rustc_hir::def::Namespace::*;
use rustc_hir::def::{DefKind, Namespace, PerNS};
use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
use rustc_hir::Mutability;
-use rustc_middle::ty::{DefIdTree, Ty, TyCtxt};
+use rustc_middle::ty::{fast_reject::TreatProjections, Ty, TyCtxt};
use rustc_middle::{bug, ty};
-use rustc_resolve::rustdoc::MalformedGenerics;
-use rustc_resolve::rustdoc::{prepare_to_doc_link_resolution, strip_generics_from_path};
+use rustc_resolve::rustdoc::{has_primitive_or_keyword_docs, prepare_to_doc_link_resolution};
+use rustc_resolve::rustdoc::{strip_generics_from_path, MalformedGenerics};
use rustc_session::lint::Lint;
use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::{sym, Ident, Symbol};
@@ -28,7 +28,7 @@ use std::mem;
use std::ops::Range;
use crate::clean::{self, utils::find_nearest_parent_module};
-use crate::clean::{Crate, Item, ItemId, ItemLink, PrimitiveType};
+use crate::clean::{Crate, Item, ItemLink, PrimitiveType};
use crate::core::DocContext;
use crate::html::markdown::{markdown_links, MarkdownLink};
use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS};
@@ -42,13 +42,23 @@ pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass = Pass {
};
fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
- let mut collector =
- LinkCollector { cx, mod_ids: Vec::new(), visited_links: FxHashMap::default() };
+ let mut collector = LinkCollector { cx, visited_links: FxHashMap::default() };
collector.visit_crate(&krate);
krate
}
-#[derive(Copy, Clone, Debug, Hash)]
+fn filter_assoc_items_by_name_and_namespace<'a>(
+ tcx: TyCtxt<'a>,
+ assoc_items_of: DefId,
+ ident: Ident,
+ ns: Namespace,
+) -> impl Iterator<Item = &ty::AssocItem> + 'a {
+ tcx.associated_items(assoc_items_of).filter_by_name_unhygienic(ident.name).filter(move |item| {
+ item.kind.namespace() == ns && tcx.hygienic_eq(ident, item.ident(tcx), assoc_items_of)
+ })
+}
+
+#[derive(Copy, Clone, Debug, Hash, PartialEq)]
enum Res {
Def(DefKind, DefId),
Primitive(PrimitiveType),
@@ -60,7 +70,7 @@ impl Res {
fn descr(self) -> &'static str {
match self {
Res::Def(kind, id) => ResolveRes::Def(kind, id).descr(),
- Res::Primitive(_) => "builtin type",
+ Res::Primitive(_) => "primitive type",
}
}
@@ -149,7 +159,7 @@ impl TryFrom<ResolveRes> for Res {
#[derive(Debug)]
struct UnresolvedPath<'a> {
/// Item on which the link is resolved, used for resolving `Self`.
- item_id: ItemId,
+ item_id: DefId,
/// The scope the link was resolved in.
module_id: DefId,
/// If part of the link resolved, this has the `Res`.
@@ -225,7 +235,7 @@ impl UrlFragment {
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct ResolutionInfo {
- item_id: ItemId,
+ item_id: DefId,
module_id: DefId,
dis: Option<Disambiguator>,
path_str: Box<str>,
@@ -242,11 +252,6 @@ struct DiagnosticInfo<'a> {
struct LinkCollector<'a, 'tcx> {
cx: &'a mut DocContext<'tcx>,
- /// A stack of modules used to decide what scope to resolve in.
- ///
- /// The last module will be used if the parent scope of the current item is
- /// unknown.
- mod_ids: Vec<DefId>,
/// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link.
/// The link will be `None` if it could not be resolved (i.e. the error was cached).
visited_links: FxHashMap<ResolutionInfo, Option<(Res, Option<UrlFragment>)>>,
@@ -262,7 +267,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
fn variant_field<'path>(
&self,
path_str: &'path str,
- item_id: ItemId,
+ item_id: DefId,
module_id: DefId,
) -> Result<(Res, DefId), UnresolvedPath<'path>> {
let tcx = self.cx.tcx;
@@ -286,7 +291,6 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
split.next().map(|f| Symbol::intern(f)).ok_or_else(no_res)?;
let path = split
.next()
- .map(|f| f.to_owned())
// If there's no third component, we saw `[a::b]` before and it failed to resolve.
// So there's no partial res.
.ok_or_else(no_res)?;
@@ -324,45 +328,50 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
prim_ty: PrimitiveType,
ns: Namespace,
item_name: Symbol,
- ) -> Option<(Res, DefId)> {
+ ) -> Vec<(Res, DefId)> {
let tcx = self.cx.tcx;
- prim_ty.impls(tcx).find_map(|impl_| {
- tcx.associated_items(impl_)
- .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, impl_)
+ prim_ty
+ .impls(tcx)
+ .flat_map(|impl_| {
+ filter_assoc_items_by_name_and_namespace(
+ tcx,
+ impl_,
+ Ident::with_dummy_span(item_name),
+ ns,
+ )
.map(|item| (Res::Primitive(prim_ty), item.def_id))
- })
+ })
+ .collect::<Vec<_>>()
}
- fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: ItemId) -> Option<Res> {
+ fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: DefId) -> Option<Res> {
if ns != TypeNS || path_str != "Self" {
return None;
}
let tcx = self.cx.tcx;
- item_id
- .as_def_id()
- .map(|def_id| match tcx.def_kind(def_id) {
- def_kind @ (DefKind::AssocFn
- | DefKind::AssocConst
- | DefKind::AssocTy
- | DefKind::Variant
- | DefKind::Field) => {
- let parent_def_id = tcx.parent(def_id);
- if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant
- {
- tcx.parent(parent_def_id)
- } else {
- parent_def_id
- }
+ let self_id = match tcx.def_kind(item_id) {
+ def_kind @ (DefKind::AssocFn
+ | DefKind::AssocConst
+ | DefKind::AssocTy
+ | DefKind::Variant
+ | DefKind::Field) => {
+ let parent_def_id = tcx.parent(item_id);
+ if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant {
+ tcx.parent(parent_def_id)
+ } else {
+ parent_def_id
}
- _ => def_id,
- })
- .and_then(|self_id| match tcx.def_kind(self_id) {
- DefKind::Impl { .. } => self.def_id_to_res(self_id),
- DefKind::Use => None,
- def_kind => Some(Res::Def(def_kind, self_id)),
- })
+ }
+ _ => item_id,
+ };
+
+ match tcx.def_kind(self_id) {
+ DefKind::Impl { .. } => self.def_id_to_res(self_id),
+ DefKind::Use => None,
+ def_kind => Some(Res::Def(def_kind, self_id)),
+ }
}
/// Convenience wrapper around `doc_link_resolutions`.
@@ -374,7 +383,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
&self,
path_str: &str,
ns: Namespace,
- item_id: ItemId,
+ item_id: DefId,
module_id: DefId,
) -> Option<Res> {
if let res @ Some(..) = self.resolve_self_ty(path_str, ns, item_id) {
@@ -401,16 +410,18 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
&mut self,
path_str: &'path str,
ns: Namespace,
- item_id: ItemId,
+ item_id: DefId,
module_id: DefId,
- ) -> Result<(Res, Option<DefId>), UnresolvedPath<'path>> {
+ ) -> Result<Vec<(Res, Option<DefId>)>, UnresolvedPath<'path>> {
if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) {
return Ok(match res {
Res::Def(
DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy | DefKind::Variant,
def_id,
- ) => (Res::from_def_id(self.cx.tcx, self.cx.tcx.parent(def_id)), Some(def_id)),
- _ => (res, None),
+ ) => {
+ vec![(Res::from_def_id(self.cx.tcx, self.cx.tcx.parent(def_id)), Some(def_id))]
+ }
+ _ => vec![(res, None)],
});
} else if ns == MacroNS {
return Err(UnresolvedPath {
@@ -429,7 +440,6 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
let item_name = Symbol::intern(item_str);
let path_root = split
.next()
- .map(|f| f.to_owned())
// If there's no `::`, it's not an associated item.
// So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
.ok_or_else(|| {
@@ -443,17 +453,24 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
})?;
// FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support
- // links to primitives when `#[doc(primitive)]` is present. It should give an ambiguity
- // error instead and special case *only* modules with `#[doc(primitive)]`, not all
+ // links to primitives when `#[rustc_doc_primitive]` is present. It should give an ambiguity
+ // error instead and special case *only* modules with `#[rustc_doc_primitive]`, not all
// primitives.
- resolve_primitive(&path_root, TypeNS)
+ match resolve_primitive(&path_root, TypeNS)
.or_else(|| self.resolve_path(&path_root, TypeNS, item_id, module_id))
.and_then(|ty_res| {
- self.resolve_associated_item(ty_res, item_name, ns, module_id).map(Ok)
- })
- .unwrap_or_else(|| {
+ let candidates = self
+ .resolve_associated_item(ty_res, item_name, ns, module_id)
+ .into_iter()
+ .map(|(res, def_id)| (res, Some(def_id)))
+ .collect::<Vec<_>>();
+ if !candidates.is_empty() { Some(candidates) } else { None }
+ }) {
+ Some(r) => Ok(r),
+ None => {
if ns == Namespace::ValueNS {
self.variant_field(path_str, item_id, module_id)
+ .map(|(res, def_id)| vec![(res, Some(def_id))])
} else {
Err(UnresolvedPath {
item_id,
@@ -462,8 +479,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
unresolved: path_root.into(),
})
}
- })
- .map(|(res, def_id)| (res, Some(def_id)))
+ }
+ }
}
/// Convert a DefId to a Res, where possible.
@@ -545,24 +562,31 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
item_name: Symbol,
ns: Namespace,
module_id: DefId,
- ) -> Option<(Res, DefId)> {
+ ) -> Vec<(Res, DefId)> {
let tcx = self.cx.tcx;
match root_res {
Res::Primitive(prim) => {
- self.resolve_primitive_associated_item(prim, ns, item_name).or_else(|| {
+ let items = self.resolve_primitive_associated_item(prim, ns, item_name);
+ if !items.is_empty() {
+ items
+ // Inherent associated items take precedence over items that come from trait impls.
+ } else {
self.primitive_type_to_ty(prim)
- .and_then(|ty| {
+ .map(|ty| {
resolve_associated_trait_item(ty, module_id, item_name, ns, self.cx)
+ .iter()
+ .map(|item| (root_res, item.def_id))
+ .collect::<Vec<_>>()
})
- .map(|item| (root_res, item.def_id))
- })
+ .unwrap_or(Vec::new())
+ }
}
Res::Def(DefKind::TyAlias, did) => {
// Resolve the link on the type the alias points to.
// FIXME: if the associated item is defined directly on the type alias,
// it will show up on its documentation page, we should link there instead.
- let res = self.def_id_to_res(did)?;
+ let Some(res) = self.def_id_to_res(did) else { return Vec::new() };
self.resolve_associated_item(res, item_name, ns, module_id)
}
Res::Def(
@@ -576,7 +600,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
ty::Adt(adt_def, _) => {
for variant in adt_def.variants() {
if variant.name == item_name {
- return Some((root_res, variant.def_id));
+ return vec![(root_res, variant.def_id)];
}
}
}
@@ -585,43 +609,46 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
}
// Checks if item_name belongs to `impl SomeItem`
- let assoc_item = tcx
+ let mut assoc_items: Vec<_> = tcx
.inherent_impls(did)
.iter()
.flat_map(|&imp| {
- tcx.associated_items(imp).find_by_name_and_namespace(
+ filter_assoc_items_by_name_and_namespace(
tcx,
+ imp,
Ident::with_dummy_span(item_name),
ns,
- imp,
)
})
- .copied()
- // There should only ever be one associated item that matches from any inherent impl
- .next()
+ .map(|item| (root_res, item.def_id))
+ .collect();
+
+ if assoc_items.is_empty() {
// Check if item_name belongs to `impl SomeTrait for SomeItem`
// FIXME(#74563): This gives precedence to `impl SomeItem`:
// Although having both would be ambiguous, use impl version for compatibility's sake.
// To handle that properly resolve() would have to support
// something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
- .or_else(|| {
- resolve_associated_trait_item(
- tcx.type_of(did).subst_identity(),
- module_id,
- item_name,
- ns,
- self.cx,
- )
- });
+ assoc_items = resolve_associated_trait_item(
+ tcx.type_of(did).subst_identity(),
+ module_id,
+ item_name,
+ ns,
+ self.cx,
+ )
+ .into_iter()
+ .map(|item| (root_res, item.def_id))
+ .collect::<Vec<_>>();
+ }
- debug!("got associated item {:?}", assoc_item);
+ debug!("got associated item {:?}", assoc_items);
- if let Some(item) = assoc_item {
- return Some((root_res, item.def_id));
+ if !assoc_items.is_empty() {
+ return assoc_items;
}
if ns != Namespace::ValueNS {
- return None;
+ return Vec::new();
}
debug!("looking for fields named {} for {:?}", item_name, did);
// FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?)
@@ -641,20 +668,27 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
// field syntax) and are handled by the compiler's resolver.
let def = match tcx.type_of(did).subst_identity().kind() {
ty::Adt(def, _) if !def.is_enum() => def,
- _ => return None,
+ _ => return Vec::new(),
};
- let field =
- def.non_enum_variant().fields.iter().find(|item| item.name == item_name)?;
- Some((root_res, field.did))
+ def.non_enum_variant()
+ .fields
+ .iter()
+ .filter(|field| field.name == item_name)
+ .map(|field| (root_res, field.did))
+ .collect::<Vec<_>>()
}
- Res::Def(DefKind::Trait, did) => tcx
- .associated_items(did)
- .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, did)
- .map(|item| {
- let res = Res::Def(item.kind.as_def_kind(), item.def_id);
- (res, item.def_id)
- }),
- _ => None,
+ Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace(
+ tcx,
+ did,
+ Ident::with_dummy_span(item_name),
+ ns,
+ )
+ .map(|item| {
+ let res = Res::Def(item.kind.as_def_kind(), item.def_id);
+ (res, item.def_id)
+ })
+ .collect::<Vec<_>>(),
+ _ => Vec::new(),
}
}
}
@@ -674,7 +708,7 @@ fn resolve_associated_trait_item<'a>(
item_name: Symbol,
ns: Namespace,
cx: &mut DocContext<'a>,
-) -> Option<ty::AssocItem> {
+) -> Vec<ty::AssocItem> {
// FIXME: this should also consider blanket impls (`impl<T> X for T`). Unfortunately
// `get_auto_trait_and_blanket_impls` is broken because the caching behavior is wrong. In the
// meantime, just don't look for these blanket impls.
@@ -682,19 +716,26 @@ fn resolve_associated_trait_item<'a>(
// Next consider explicit impls: `impl MyTrait for MyType`
// Give precedence to inherent impls.
let traits = trait_impls_for(cx, ty, module);
+ let tcx = cx.tcx;
debug!("considering traits {:?}", traits);
- let mut candidates = traits.iter().filter_map(|&(impl_, trait_)| {
- cx.tcx
- .associated_items(trait_)
- .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_)
- .map(|trait_assoc| {
- trait_assoc_to_impl_assoc_item(cx.tcx, impl_, trait_assoc.def_id)
+ let candidates = traits
+ .iter()
+ .flat_map(|&(impl_, trait_)| {
+ filter_assoc_items_by_name_and_namespace(
+ cx.tcx,
+ trait_,
+ Ident::with_dummy_span(item_name),
+ ns,
+ )
+ .map(move |trait_assoc| {
+ trait_assoc_to_impl_assoc_item(tcx, impl_, trait_assoc.def_id)
.unwrap_or(*trait_assoc)
})
- });
+ })
+ .collect::<Vec<_>>();
// FIXME(#74563): warn about ambiguity
- debug!("the candidates were {:?}", candidates.clone().collect::<Vec<_>>());
- candidates.next()
+ debug!("the candidates were {:?}", candidates);
+ candidates
}
/// Find the associated item in the impl `impl_id` that corresponds to the
@@ -735,7 +776,7 @@ fn trait_impls_for<'a>(
trace!("considering explicit impl for trait {:?}", trait_);
// Look at each trait implementation to see if it's an impl for `did`
- tcx.find_map_relevant_impl(trait_, ty, |impl_| {
+ tcx.find_map_relevant_impl(trait_, ty, TreatProjections::ForLookup, |impl_| {
let trait_ref = tcx.impl_trait_ref(impl_).expect("this is not an inherent impl");
// Check if these are the same type.
let impl_type = trait_ref.skip_binder().self_ty();
@@ -768,61 +809,21 @@ fn trait_impls_for<'a>(
/// Check for resolve collisions between a trait and its derive.
///
/// These are common and we should just resolve to the trait in that case.
-fn is_derive_trait_collision<T>(ns: &PerNS<Result<(Res, T), ResolutionFailure<'_>>>) -> bool {
- matches!(
- *ns,
- PerNS {
- type_ns: Ok((Res::Def(DefKind::Trait, _), _)),
- macro_ns: Ok((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)),
- ..
- }
- )
+fn is_derive_trait_collision<T>(ns: &PerNS<Result<Vec<(Res, T)>, ResolutionFailure<'_>>>) -> bool {
+ if let (Ok(type_ns), Ok(macro_ns)) = (&ns.type_ns, &ns.macro_ns) {
+ type_ns.iter().any(|(res, _)| matches!(res, Res::Def(DefKind::Trait, _)))
+ && macro_ns
+ .iter()
+ .any(|(res, _)| matches!(res, Res::Def(DefKind::Macro(MacroKind::Derive), _)))
+ } else {
+ false
+ }
}
impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
fn visit_item(&mut self, item: &Item) {
- let parent_node =
- item.item_id.as_def_id().and_then(|did| find_nearest_parent_module(self.cx.tcx, did));
- if parent_node.is_some() {
- trace!("got parent node for {:?} {:?}, id {:?}", item.type_(), item.name, item.item_id);
- }
-
- let inner_docs = item.inner_docs(self.cx.tcx);
-
- if item.is_mod() && inner_docs {
- self.mod_ids.push(item.item_id.expect_def_id());
- }
-
- // We want to resolve in the lexical scope of the documentation.
- // In the presence of re-exports, this is not the same as the module of the item.
- // Rather than merging all documentation into one, resolve it one attribute at a time
- // so we know which module it came from.
- for (parent_module, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) {
- if !may_have_doc_links(&doc) {
- continue;
- }
- debug!("combined_docs={}", doc);
- // NOTE: if there are links that start in one crate and end in another, this will not resolve them.
- // This is a degenerate case and it's not supported by rustdoc.
- let parent_node = parent_module.or(parent_node);
- for md_link in preprocessed_markdown_links(&doc) {
- let link = self.resolve_link(item, &doc, parent_node, &md_link);
- if let Some(link) = link {
- self.cx.cache.intra_doc_links.entry(item.item_id).or_default().push(link);
- }
- }
- }
-
- if item.is_mod() {
- if !inner_docs {
- self.mod_ids.push(item.item_id.expect_def_id());
- }
-
- self.visit_item_recur(item);
- self.mod_ids.pop();
- } else {
- self.visit_item_recur(item)
- }
+ self.resolve_links(item);
+ self.visit_item_recur(item)
}
}
@@ -948,14 +949,50 @@ fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> {
}
impl LinkCollector<'_, '_> {
+ fn resolve_links(&mut self, item: &Item) {
+ if !self.cx.render_options.document_private
+ && let Some(def_id) = item.item_id.as_def_id()
+ && let Some(def_id) = def_id.as_local()
+ && !self.cx.tcx.effective_visibilities(()).is_exported(def_id)
+ && !has_primitive_or_keyword_docs(&item.attrs.other_attrs) {
+ // Skip link resolution for non-exported items.
+ return;
+ }
+
+ // We want to resolve in the lexical scope of the documentation.
+ // In the presence of re-exports, this is not the same as the module of the item.
+ // Rather than merging all documentation into one, resolve it one attribute at a time
+ // so we know which module it came from.
+ for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) {
+ if !may_have_doc_links(&doc) {
+ continue;
+ }
+ debug!("combined_docs={}", doc);
+ // NOTE: if there are links that start in one crate and end in another, this will not resolve them.
+ // This is a degenerate case and it's not supported by rustdoc.
+ let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id());
+ let module_id = match self.cx.tcx.def_kind(item_id) {
+ DefKind::Mod if item.inner_docs(self.cx.tcx) => item_id,
+ _ => find_nearest_parent_module(self.cx.tcx, item_id).unwrap(),
+ };
+ for md_link in preprocessed_markdown_links(&doc) {
+ let link = self.resolve_link(item, item_id, module_id, &doc, &md_link);
+ if let Some(link) = link {
+ self.cx.cache.intra_doc_links.entry(item.item_id).or_default().insert(link);
+ }
+ }
+ }
+ }
+
/// This is the entry point for resolving an intra-doc link.
///
/// FIXME(jynelson): this is way too many arguments
fn resolve_link(
&mut self,
item: &Item,
+ item_id: DefId,
+ module_id: DefId,
dox: &str,
- parent_node: Option<DefId>,
link: &PreprocessedMarkdownLink,
) -> Option<ItemLink> {
let PreprocessedMarkdownLink(pp_link, ori_link) = link;
@@ -972,25 +1009,9 @@ impl LinkCollector<'_, '_> {
pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?;
let disambiguator = *disambiguator;
- // In order to correctly resolve intra-doc links we need to
- // pick a base AST node to work from. If the documentation for
- // this module came from an inner comment (//!) then we anchor
- // our name resolution *inside* the module. If, on the other
- // hand it was an outer comment (///) then we anchor the name
- // resolution in the parent module on the basis that the names
- // used are more likely to be intended to be parent names. For
- // this, we set base_node to None for inner comments since
- // we've already pushed this node onto the resolution stack but
- // for outer comments we explicitly try and resolve against the
- // parent_node first.
- let inner_docs = item.inner_docs(self.cx.tcx);
- let base_node =
- if item.is_mod() && inner_docs { self.mod_ids.last().copied() } else { parent_node };
- let module_id = base_node.expect("doc link without parent module");
-
let (mut res, fragment) = self.resolve_with_disambiguator_cached(
ResolutionInfo {
- item_id: item.item_id,
+ item_id,
module_id,
dis: disambiguator,
path_str: path_str.clone(),
@@ -1017,15 +1038,15 @@ impl LinkCollector<'_, '_> {
res = prim;
} else {
// `[char]` when a `char` module is in scope
- let candidates = vec![res, prim];
- ambiguity_error(self.cx, diag_info, path_str, candidates);
+ let candidates = &[(res, res.def_id(self.cx.tcx)), (prim, None)];
+ ambiguity_error(self.cx, &diag_info, path_str, candidates);
return None;
}
}
}
match res {
- Res::Primitive(prim) => {
+ Res::Primitive(_) => {
if let Some(UrlFragment::Item(id)) = fragment {
// We're actually resolving an associated item of a primitive, so we need to
// verify the disambiguator (if any) matches the type of the associated item.
@@ -1045,15 +1066,6 @@ impl LinkCollector<'_, '_> {
item,
&diag_info,
)?;
-
- // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
- // However I'm not sure how to check that across crates.
- if prim == PrimitiveType::RawPointer
- && item.item_id.is_local()
- && !self.cx.tcx.features().intra_doc_pointers
- {
- self.report_rawptr_assoc_feature_gate(dox, ori_link, item);
- }
} else {
match disambiguator {
Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {}
@@ -1132,7 +1144,7 @@ impl LinkCollector<'_, '_> {
}
}
- // item can be non-local e.g. when using #[doc(primitive = "pointer")]
+ // item can be non-local e.g. when using `#[rustc_doc_primitive = "pointer"]`
if let Some((src_id, dst_id)) = id.as_local().and_then(|dst_id| {
item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id))
}) {
@@ -1174,10 +1186,9 @@ impl LinkCollector<'_, '_> {
report_diagnostic(self.cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, diag_info, callback);
}
- fn report_rawptr_assoc_feature_gate(&self, dox: &str, ori_link: &MarkdownLink, item: &Item) {
- let span =
- super::source_span_for_markdown_range(self.cx.tcx, dox, &ori_link.range, &item.attrs)
- .unwrap_or_else(|| item.attr_span(self.cx.tcx));
+ fn report_rawptr_assoc_feature_gate(&self, dox: &str, ori_link: &Range<usize>, item: &Item) {
+ let span = super::source_span_for_markdown_range(self.cx.tcx, dox, ori_link, &item.attrs)
+ .unwrap_or_else(|| item.attr_span(self.cx.tcx));
rustc_session::parse::feature_err(
&self.cx.tcx.sess.parse_sess,
sym::intra_doc_pointers,
@@ -1202,7 +1213,31 @@ impl LinkCollector<'_, '_> {
}
}
- let res = self.resolve_with_disambiguator(&key, diag.clone()).and_then(|(res, def_id)| {
+ let mut candidates = self.resolve_with_disambiguator(&key, diag.clone());
+
+ // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
+ // However I'm not sure how to check that across crates.
+ if let Some(candidate) = candidates.get(0) &&
+ candidate.0 == Res::Primitive(PrimitiveType::RawPointer) &&
+ key.path_str.contains("::") // We only want to check this if this is an associated item.
+ {
+ if key.item_id.is_local() && !self.cx.tcx.features().intra_doc_pointers {
+ self.report_rawptr_assoc_feature_gate(diag.dox, &diag.link_range, diag.item);
+ return None;
+ } else {
+ candidates = vec![candidates[0]];
+ }
+ }
+
+ // If there are multiple items with the same "kind" (for example, both "associated types")
+ // and after removing duplicated kinds, only one remains, the `ambiguity_error` function
+ // won't emit an error. So at this point, we can just take the first candidate as it was
+ // the first retrieved and use it to generate the link.
+ if candidates.len() > 1 && !ambiguity_error(self.cx, &diag, &key.path_str, &candidates) {
+ candidates = vec![candidates[0]];
+ }
+
+ if let &[(res, def_id)] = candidates.as_slice() {
let fragment = match (&key.extra_fragment, def_id) {
(Some(_), Some(def_id)) => {
report_anchor_conflict(self.cx, diag, def_id);
@@ -1212,13 +1247,15 @@ impl LinkCollector<'_, '_> {
(None, Some(def_id)) => Some(UrlFragment::Item(def_id)),
(None, None) => None,
};
- Some((res, fragment))
- });
+ let r = Some((res, fragment));
+ self.visited_links.insert(key, r.clone());
+ return r;
+ }
- if res.is_some() || cache_errors {
- self.visited_links.insert(key, res.clone());
+ if cache_errors {
+ self.visited_links.insert(key, None);
}
- res
+ None
}
/// After parsing the disambiguator, resolve the main part of the link.
@@ -1227,16 +1264,16 @@ impl LinkCollector<'_, '_> {
&mut self,
key: &ResolutionInfo,
diag: DiagnosticInfo<'_>,
- ) -> Option<(Res, Option<DefId>)> {
+ ) -> Vec<(Res, Option<DefId>)> {
let disambiguator = key.dis;
let path_str = &key.path_str;
let item_id = key.item_id;
- let base_node = key.module_id;
+ let module_id = key.module_id;
match disambiguator.map(Disambiguator::ns) {
Some(expected_ns) => {
- match self.resolve(path_str, expected_ns, item_id, base_node) {
- Ok(res) => Some(res),
+ match self.resolve(path_str, expected_ns, item_id, module_id) {
+ Ok(candidates) => candidates,
Err(err) => {
// We only looked in one namespace. Try to give a better error if possible.
// FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`.
@@ -1245,10 +1282,11 @@ impl LinkCollector<'_, '_> {
for other_ns in [TypeNS, ValueNS, MacroNS] {
if other_ns != expected_ns {
if let Ok(res) =
- self.resolve(path_str, other_ns, item_id, base_node)
+ self.resolve(path_str, other_ns, item_id, module_id) &&
+ !res.is_empty()
{
err = ResolutionFailure::WrongNamespace {
- res: full_res(self.cx.tcx, res),
+ res: full_res(self.cx.tcx, res[0]),
expected_ns,
};
break;
@@ -1262,25 +1300,33 @@ impl LinkCollector<'_, '_> {
None => {
// Try everything!
let mut candidate = |ns| {
- self.resolve(path_str, ns, item_id, base_node)
+ self.resolve(path_str, ns, item_id, module_id)
.map_err(ResolutionFailure::NotResolved)
};
let candidates = PerNS {
macro_ns: candidate(MacroNS),
type_ns: candidate(TypeNS),
- value_ns: candidate(ValueNS).and_then(|(res, def_id)| {
- match res {
- // Constructors are picked up in the type namespace.
- Res::Def(DefKind::Ctor(..), _) => {
- Err(ResolutionFailure::WrongNamespace { res, expected_ns: TypeNS })
+ value_ns: candidate(ValueNS).and_then(|v_res| {
+ for (res, _) in v_res.iter() {
+ match res {
+ // Constructors are picked up in the type namespace.
+ Res::Def(DefKind::Ctor(..), _) => {
+ return Err(ResolutionFailure::WrongNamespace {
+ res: *res,
+ expected_ns: TypeNS,
+ });
+ }
+ _ => {}
}
- _ => Ok((res, def_id)),
}
+ Ok(v_res)
}),
};
- let len = candidates.iter().filter(|res| res.is_ok()).count();
+ let len = candidates
+ .iter()
+ .fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc });
if len == 0 {
return resolution_failure(
@@ -1290,22 +1336,21 @@ impl LinkCollector<'_, '_> {
disambiguator,
candidates.into_iter().filter_map(|res| res.err()).collect(),
);
- }
-
- if len == 1 {
- Some(candidates.into_iter().find_map(|res| res.ok()).unwrap())
- } else if len == 2 && is_derive_trait_collision(&candidates) {
- Some(candidates.type_ns.unwrap())
+ } else if len == 1 {
+ candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>()
} else {
- let ignore_macro = is_derive_trait_collision(&candidates);
- // If we're reporting an ambiguity, don't mention the namespaces that failed
- let mut candidates =
- candidates.map(|candidate| candidate.ok().map(|(res, _)| res));
- if ignore_macro {
- candidates.macro_ns = None;
+ let has_derive_trait_collision = is_derive_trait_collision(&candidates);
+ if len == 2 && has_derive_trait_collision {
+ candidates.type_ns.unwrap()
+ } else {
+ // If we're reporting an ambiguity, don't mention the namespaces that failed
+ let mut candidates = candidates.map(|candidate| candidate.ok());
+ // If there a collision between a trait and a derive, we ignore the derive.
+ if has_derive_trait_collision {
+ candidates.macro_ns = None;
+ }
+ candidates.into_iter().filter_map(|res| res).flatten().collect::<Vec<_>>()
}
- ambiguity_error(self.cx, diag, path_str, candidates.present_items().collect());
- None
}
}
}
@@ -1593,7 +1638,7 @@ fn resolution_failure(
path_str: &str,
disambiguator: Option<Disambiguator>,
kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
-) -> Option<(Res, Option<DefId>)> {
+) -> Vec<(Res, Option<DefId>)> {
let tcx = collector.cx.tcx;
let mut recovered_res = None;
report_diagnostic(
@@ -1652,11 +1697,13 @@ fn resolution_failure(
};
name = start;
for ns in [TypeNS, ValueNS, MacroNS] {
- if let Ok(res) = collector.resolve(start, ns, item_id, module_id) {
- debug!("found partial_res={:?}", res);
- *partial_res = Some(full_res(collector.cx.tcx, res));
- *unresolved = end.into();
- break 'outer;
+ if let Ok(v_res) = collector.resolve(start, ns, item_id, module_id) {
+ debug!("found partial_res={:?}", v_res);
+ if !v_res.is_empty() {
+ *partial_res = Some(full_res(collector.cx.tcx, v_res[0]));
+ *unresolved = end.into();
+ break 'outer;
+ }
}
}
*unresolved = end.into();
@@ -1804,7 +1851,10 @@ fn resolution_failure(
},
);
- recovered_res
+ match recovered_res {
+ Some(r) => vec![r],
+ None => Vec::new(),
+ }
}
fn report_multiple_anchors(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) {
@@ -1889,28 +1939,47 @@ fn report_malformed_generics(
}
/// Report an ambiguity error, where there were multiple possible resolutions.
+///
+/// If all `candidates` have the same kind, it's not possible to disambiguate so in this case,
+/// the function won't emit an error and will return `false`. Otherwise, it'll emit the error and
+/// return `true`.
fn ambiguity_error(
cx: &DocContext<'_>,
- diag_info: DiagnosticInfo<'_>,
+ diag_info: &DiagnosticInfo<'_>,
path_str: &str,
- candidates: Vec<Res>,
-) {
- let mut msg = format!("`{}` is ", path_str);
+ candidates: &[(Res, Option<DefId>)],
+) -> bool {
+ let mut descrs = FxHashSet::default();
+ let kinds = candidates
+ .iter()
+ .map(
+ |(res, def_id)| {
+ if let Some(def_id) = def_id { Res::from_def_id(cx.tcx, *def_id) } else { *res }
+ },
+ )
+ .filter(|res| descrs.insert(res.descr()))
+ .collect::<Vec<_>>();
+ if descrs.len() == 1 {
+ // There is no way for users to disambiguate at this point, so better return the first
+ // candidate and not show a warning.
+ return false;
+ }
- match candidates.as_slice() {
- [first_def, second_def] => {
+ let mut msg = format!("`{}` is ", path_str);
+ match kinds.as_slice() {
+ [res1, res2] => {
msg += &format!(
"both {} {} and {} {}",
- first_def.article(),
- first_def.descr(),
- second_def.article(),
- second_def.descr(),
+ res1.article(),
+ res1.descr(),
+ res2.article(),
+ res2.descr()
);
}
_ => {
- let mut candidates = candidates.iter().peekable();
- while let Some(res) = candidates.next() {
- if candidates.peek().is_some() {
+ let mut kinds = kinds.iter().peekable();
+ while let Some(res) = kinds.next() {
+ if kinds.peek().is_some() {
msg += &format!("{} {}, ", res.article(), res.descr());
} else {
msg += &format!("and {} {}", res.article(), res.descr());
@@ -1919,17 +1988,18 @@ fn ambiguity_error(
}
}
- report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, |diag, sp| {
+ report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, diag_info, |diag, sp| {
if let Some(sp) = sp {
diag.span_label(sp, "ambiguous link");
} else {
diag.note("ambiguous link");
}
- for res in candidates {
+ for res in kinds {
suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp);
}
});
+ true
}
/// In case of an ambiguity or mismatched disambiguator, suggest the correct
diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs
index 01ed4a60b..8d204ddb7 100644
--- a/src/librustdoc/passes/collect_trait_impls.rs
+++ b/src/librustdoc/passes/collect_trait_impls.rs
@@ -9,7 +9,7 @@ use crate::visit::DocVisitor;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE};
-use rustc_middle::ty::{self, DefIdTree};
+use rustc_middle::ty;
use rustc_span::symbol::sym;
pub(crate) const COLLECT_TRAIT_IMPLS: Pass = Pass {
@@ -49,7 +49,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
let _prof_timer = cx.tcx.sess.prof.generic_activity("build_extern_trait_impls");
for &cnum in cx.tcx.crates(()) {
for &impl_def_id in cx.tcx.trait_impls_in_crate(cnum) {
- inline::build_impl(cx, None, impl_def_id, None, &mut new_items_external);
+ inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
}
}
}
@@ -75,7 +75,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
);
parent = cx.tcx.opt_parent(did);
}
- inline::build_impl(cx, None, impl_def_id, Some(&attr_buf), &mut new_items_local);
+ inline::build_impl(cx, impl_def_id, Some((&attr_buf, None)), &mut new_items_local);
attr_buf.clear();
}
}
@@ -84,7 +84,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
for def_id in PrimitiveType::all_impls(cx.tcx) {
// Try to inline primitive impls from other crates.
if !def_id.is_local() {
- inline::build_impl(cx, None, def_id, None, &mut new_items_external);
+ inline::build_impl(cx, def_id, None, &mut new_items_external);
}
}
for (prim, did) in PrimitiveType::primitive_locations(cx.tcx) {
diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs
index a4bc48690..8a33e51b3 100644
--- a/src/librustdoc/passes/propagate_doc_cfg.rs
+++ b/src/librustdoc/passes/propagate_doc_cfg.rs
@@ -9,7 +9,6 @@ use crate::fold::DocFolder;
use crate::passes::Pass;
use rustc_hir::def_id::LocalDefId;
-use rustc_middle::ty::DefIdTree;
pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass {
name: "propagate-doc-cfg",
@@ -58,7 +57,8 @@ impl<'a, 'tcx> CfgPropagator<'a, 'tcx> {
next_def_id = parent_def_id;
}
- let (_, cfg) = merge_attrs(self.cx, None, item.attrs.other_attrs.as_slice(), Some(&attrs));
+ let (_, cfg) =
+ merge_attrs(self.cx, item.attrs.other_attrs.as_slice(), Some((&attrs, None)));
item.cfg = cfg;
}
}
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index 890b3e8d6..a688aa148 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -121,9 +121,14 @@ impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> {
// strip things like impl methods but when doing so
// we must not add any items to the `retained` set.
let old = mem::replace(&mut self.update_retained, false);
- let ret = strip_item(self.set_is_in_hidden_item_and_fold(true, i));
+ let ret = self.set_is_in_hidden_item_and_fold(true, i);
self.update_retained = old;
- Some(ret)
+ if ret.is_crate() {
+ // We don't strip the crate, even if it has `#[doc(hidden)]`.
+ Some(ret)
+ } else {
+ Some(strip_item(ret))
+ }
}
_ => {
let ret = self.set_is_in_hidden_item_and_fold(true, i);
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 157e042e4..f54b70b41 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -8,14 +8,14 @@ use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet};
use rustc_hir::intravisit::{walk_item, Visitor};
use rustc_hir::{Node, CRATE_HIR_ID};
use rustc_middle::hir::nested_filter;
-use rustc_middle::ty::{DefIdTree, TyCtxt};
+use rustc_middle::ty::TyCtxt;
use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::Span;
-use std::mem;
+use std::{iter, mem};
-use crate::clean::{cfg::Cfg, AttributesExt, NestedAttributesExt};
+use crate::clean::{cfg::Cfg, reexport_chain, AttributesExt, NestedAttributesExt};
use crate::core;
/// This module is used to store stuff from Rust's AST in a more convenient
@@ -133,7 +133,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
// is declared but also a reexport of itself producing two exports of the same
// macro in the same module.
let mut inserted = FxHashSet::default();
- for export in self.cx.tcx.module_reexports(CRATE_DEF_ID).unwrap_or(&[]) {
+ for export in self.cx.tcx.module_children_reexports(CRATE_DEF_ID) {
if let Res::Def(DefKind::Macro(_), def_id) = export.res &&
let Some(local_def_id) = def_id.as_local() &&
self.cx.tcx.has_attr(def_id, sym::macro_export) &&
@@ -223,6 +223,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
) -> bool {
debug!("maybe_inline_local res: {:?}", res);
+ if renamed == Some(kw::Underscore) {
+ // We never inline `_` reexports.
+ return false;
+ }
+
if self.cx.output_format.is_json() {
return false;
}
@@ -259,6 +264,22 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
return false;
}
+ if !please_inline &&
+ let Some(item_def_id) = reexport_chain(self.cx.tcx, def_id, res_did).iter()
+ .flat_map(|reexport| reexport.id()).map(|id| id.expect_local())
+ .chain(iter::once(res_did)).nth(1) &&
+ item_def_id != def_id &&
+ self
+ .cx
+ .cache
+ .effective_visibilities
+ .is_directly_public(self.cx.tcx, item_def_id.to_def_id()) &&
+ !inherits_doc_hidden(self.cx.tcx, item_def_id)
+ {
+ // The imported item is public and not `doc(hidden)` so no need to inline it.
+ return false;
+ }
+
if !self.view_item_stack.insert(res_did) {
return false;
}
@@ -329,8 +350,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
self.visit_foreign_item_inner(item, None);
}
}
- // If we're inlining, skip private items or item reexported as "_".
- _ if self.inlining && (!is_pub || renamed == Some(kw::Underscore)) => {}
+ // If we're inlining, skip private items.
+ _ if self.inlining && !is_pub => {}
hir::ItemKind::GlobalAsm(..) => {}
hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
hir::ItemKind::Use(path, kind) => {