summaryrefslogtreecommitdiffstats
path: root/src/librustdoc
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc')
-rw-r--r--src/librustdoc/clean/auto_trait.rs12
-rw-r--r--src/librustdoc/clean/blanket_impl.rs20
-rw-r--r--src/librustdoc/clean/inline.rs25
-rw-r--r--src/librustdoc/clean/mod.rs431
-rw-r--r--src/librustdoc/clean/simplify.rs2
-rw-r--r--src/librustdoc/clean/types.rs77
-rw-r--r--src/librustdoc/clean/utils.rs59
-rw-r--r--src/librustdoc/config.rs36
-rw-r--r--src/librustdoc/core.rs7
-rw-r--r--src/librustdoc/doctest.rs11
-rw-r--r--src/librustdoc/formats/cache.rs5
-rw-r--r--src/librustdoc/html/format.rs58
-rw-r--r--src/librustdoc/html/layout.rs2
-rw-r--r--src/librustdoc/html/markdown.rs18
-rw-r--r--src/librustdoc/html/render/context.rs41
-rw-r--r--src/librustdoc/html/render/mod.rs29
-rw-r--r--src/librustdoc/html/render/print_item.rs267
-rw-r--r--src/librustdoc/html/render/search_index.rs23
-rw-r--r--src/librustdoc/html/render/type_layout.rs2
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css54
-rw-r--r--src/librustdoc/html/static/js/externs.js4
-rw-r--r--src/librustdoc/html/static/js/main.js170
-rw-r--r--src/librustdoc/html/static/js/search.js894
-rw-r--r--src/librustdoc/html/static/js/source-script.js4
-rw-r--r--src/librustdoc/html/static/js/storage.js2
-rw-r--r--src/librustdoc/html/templates/item_info.html2
-rw-r--r--src/librustdoc/html/templates/item_union.html19
-rw-r--r--src/librustdoc/html/templates/page.html4
-rw-r--r--src/librustdoc/html/templates/print_item.html4
-rw-r--r--src/librustdoc/json/conversions.rs5
-rw-r--r--src/librustdoc/lib.rs55
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs8
-rw-r--r--src/librustdoc/passes/lint/bare_urls.rs29
-rw-r--r--src/librustdoc/passes/lint/html_tags.rs170
-rw-r--r--src/librustdoc/passes/lint/unescaped_backticks.rs2
-rw-r--r--src/librustdoc/passes/strip_hidden.rs4
-rw-r--r--src/librustdoc/passes/stripper.rs9
-rw-r--r--src/librustdoc/visit_ast.rs91
38 files changed, 1700 insertions, 955 deletions
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs
index baf2b0a85..92bae5516 100644
--- a/src/librustdoc/clean/auto_trait.rs
+++ b/src/librustdoc/clean/auto_trait.rs
@@ -124,7 +124,7 @@ where
unsafety: hir::Unsafety::Normal,
generics: new_generics,
trait_: Some(clean_trait_ref_with_bindings(self.cx, trait_ref, ThinVec::new())),
- for_: clean_middle_ty(ty::Binder::dummy(ty), self.cx, None),
+ for_: clean_middle_ty(ty::Binder::dummy(ty), self.cx, None, None),
items: Vec::new(),
polarity,
kind: ImplKind::Auto,
@@ -317,14 +317,14 @@ where
lifetime_predicates
}
- fn extract_for_generics(&self, pred: ty::Predicate<'tcx>) -> FxHashSet<GenericParamDef> {
+ fn extract_for_generics(&self, pred: ty::Clause<'tcx>) -> FxHashSet<GenericParamDef> {
let bound_predicate = pred.kind();
let tcx = self.cx.tcx;
let regions = match bound_predicate.skip_binder() {
- ty::PredicateKind::Clause(ty::Clause::Trait(poly_trait_pred)) => {
+ ty::ClauseKind::Trait(poly_trait_pred) => {
tcx.collect_referenced_late_bound_regions(&bound_predicate.rebind(poly_trait_pred))
}
- ty::PredicateKind::Clause(ty::Clause::Projection(poly_proj_pred)) => {
+ ty::ClauseKind::Projection(poly_proj_pred) => {
tcx.collect_referenced_late_bound_regions(&bound_predicate.rebind(poly_proj_pred))
}
_ => return FxHashSet::default(),
@@ -449,9 +449,7 @@ where
.filter(|p| {
!orig_bounds.contains(p)
|| match p.kind().skip_binder() {
- ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => {
- pred.def_id() == sized_trait
- }
+ ty::ClauseKind::Trait(pred) => pred.def_id() == sized_trait,
_ => false,
}
})
diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs
index e4c05b573..a36041588 100644
--- a/src/librustdoc/clean/blanket_impl.rs
+++ b/src/librustdoc/clean/blanket_impl.rs
@@ -21,7 +21,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
let mut impls = Vec::new();
for trait_def_id in cx.tcx.all_traits() {
if !cx.cache.effective_visibilities.is_reachable(cx.tcx, trait_def_id)
- || cx.generated_synthetics.get(&(ty.0, trait_def_id)).is_some()
+ || cx.generated_synthetics.get(&(ty.skip_binder(), trait_def_id)).is_some()
{
continue;
}
@@ -34,13 +34,13 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
impl_def_id
);
let trait_ref = cx.tcx.impl_trait_ref(impl_def_id).unwrap();
- if !matches!(trait_ref.0.self_ty().kind(), ty::Param(_)) {
+ if !matches!(trait_ref.skip_binder().self_ty().kind(), ty::Param(_)) {
continue;
}
let infcx = cx.tcx.infer_ctxt().build();
let substs = infcx.fresh_substs_for_item(DUMMY_SP, item_def_id);
let impl_ty = ty.subst(infcx.tcx, substs);
- let param_env = EarlyBinder(param_env).subst(infcx.tcx, substs);
+ let param_env = EarlyBinder::bind(param_env).subst(infcx.tcx, substs);
let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
let impl_trait_ref = trait_ref.subst(infcx.tcx, impl_substs);
@@ -87,7 +87,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
trait_ref, ty
);
- cx.generated_synthetics.insert((ty.0, trait_def_id));
+ cx.generated_synthetics.insert((ty.skip_binder(), trait_def_id));
impls.push(Item {
name: None,
@@ -104,10 +104,15 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
// the post-inference `trait_ref`, as it's more accurate.
trait_: Some(clean_trait_ref_with_bindings(
cx,
- ty::Binder::dummy(trait_ref.0),
+ ty::Binder::dummy(trait_ref.subst_identity()),
ThinVec::new(),
)),
- for_: clean_middle_ty(ty::Binder::dummy(ty.0), cx, None),
+ for_: clean_middle_ty(
+ ty::Binder::dummy(ty.subst_identity()),
+ cx,
+ None,
+ None,
+ ),
items: cx
.tcx
.associated_items(impl_def_id)
@@ -116,9 +121,10 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
.collect::<Vec<_>>(),
polarity: ty::ImplPolarity::Positive,
kind: ImplKind::Blanket(Box::new(clean_middle_ty(
- ty::Binder::dummy(trait_ref.0.self_ty()),
+ ty::Binder::dummy(trait_ref.subst_identity().self_ty()),
cx,
None,
+ None,
))),
}))),
cfg: None,
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 7dc08b3b1..870cfa930 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -158,13 +158,13 @@ pub(crate) fn try_inline_glob(
.filter_map(|child| child.res.opt_def_id())
.collect();
let mut items = build_module_items(cx, did, visited, inlined_names, Some(&reexports));
- items.drain_filter(|item| {
+ items.retain(|item| {
if let Some(name) = item.name {
// If an item with the same type and name already exists,
// it takes priority over the inlined stuff.
- !inlined_names.insert((item.type_(), name))
+ inlined_names.insert((item.type_(), name))
} else {
- false
+ true
}
});
Some(items)
@@ -278,8 +278,12 @@ fn build_union(cx: &mut DocContext<'_>, did: DefId) -> clean::Union {
fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box<clean::Typedef> {
let predicates = cx.tcx.explicit_predicates_of(did);
- let type_ =
- clean_middle_ty(ty::Binder::dummy(cx.tcx.type_of(did).subst_identity()), cx, Some(did));
+ let type_ = clean_middle_ty(
+ ty::Binder::dummy(cx.tcx.type_of(did).subst_identity()),
+ cx,
+ Some(did),
+ None,
+ );
Box::new(clean::Typedef {
type_,
@@ -386,9 +390,12 @@ pub(crate) fn build_impl(
let for_ = match &impl_item {
Some(impl_) => clean_ty(impl_.self_ty, cx),
- None => {
- clean_middle_ty(ty::Binder::dummy(tcx.type_of(did).subst_identity()), cx, Some(did))
- }
+ None => clean_middle_ty(
+ ty::Binder::dummy(tcx.type_of(did).subst_identity()),
+ cx,
+ Some(did),
+ None,
+ ),
};
// Only inline impl if the implementing type is
@@ -630,6 +637,7 @@ fn build_const(cx: &mut DocContext<'_>, def_id: DefId) -> clean::Constant {
ty::Binder::dummy(cx.tcx.type_of(def_id).subst_identity()),
cx,
Some(def_id),
+ None,
),
kind: clean::ConstantKind::Extern { def_id },
}
@@ -641,6 +649,7 @@ fn build_static(cx: &mut DocContext<'_>, did: DefId, mutable: bool) -> clean::St
ty::Binder::dummy(cx.tcx.type_of(did).subst_identity()),
cx,
Some(did),
+ None,
),
mutability: if mutable { Mutability::Mut } else { Mutability::Not },
expr: None,
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 03adc19e3..8ad1ed095 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -31,6 +31,7 @@ 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 rustc_trait_selection::traits::wf::object_region_bounds;
use std::borrow::Cow;
use std::collections::hash_map::Entry;
@@ -53,7 +54,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
let mut inserted = FxHashSet::default();
items.extend(doc.foreigns.iter().map(|(item, renamed)| {
let item = clean_maybe_renamed_foreign_item(cx, item, *renamed);
- if let Some(name) = item.name && !item.attrs.lists(sym::doc).has_word(sym::hidden) {
+ if let Some(name) = item.name && !item.is_doc_hidden() {
inserted.insert((item.type_(), name));
}
item
@@ -63,7 +64,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
return None;
}
let item = clean_doc_module(x, cx);
- if item.attrs.lists(sym::doc).has_word(sym::hidden) {
+ if item.is_doc_hidden() {
// Hidden modules are stripped at a later stage.
// If a hidden module has the same name as a visible one, we want
// to keep both of them around.
@@ -84,7 +85,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
}
let v = clean_maybe_renamed_item(cx, item, *renamed, *import_id);
for item in &v {
- if let Some(name) = item.name && !item.attrs.lists(sym::doc).has_word(sym::hidden) {
+ if let Some(name) = item.name && !item.is_doc_hidden() {
inserted.insert((item.type_(), name));
}
}
@@ -166,8 +167,7 @@ fn clean_generic_bound<'tcx>(
let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(cx.tcx, def_id));
let generic_args = clean_generic_args(generic_args, cx);
- let GenericArgs::AngleBracketed { bindings, .. } = generic_args
- else {
+ let GenericArgs::AngleBracketed { bindings, .. } = generic_args else {
bug!("clean: parenthesized `GenericBound::LangItemTrait`");
};
@@ -253,6 +253,7 @@ pub(crate) fn clean_const<'tcx>(constant: &hir::ConstArg, cx: &mut DocContext<'t
ty::Binder::dummy(cx.tcx.type_of(def_id).subst_identity()),
cx,
Some(def_id),
+ None,
),
kind: ConstantKind::Anonymous { body: constant.value.body },
}
@@ -264,7 +265,7 @@ pub(crate) fn clean_middle_const<'tcx>(
) -> Constant {
// FIXME: instead of storing the stringified expression, store `self` directly instead.
Constant {
- type_: clean_middle_ty(constant.map_bound(|c| c.ty()), cx, None),
+ type_: clean_middle_ty(constant.map_bound(|c| c.ty()), cx, None, None),
kind: ConstantKind::TyConst { expr: constant.skip_binder().to_string().into() },
}
}
@@ -324,36 +325,23 @@ fn clean_where_predicate<'tcx>(
}
pub(crate) fn clean_predicate<'tcx>(
- predicate: ty::Predicate<'tcx>,
+ predicate: ty::Clause<'tcx>,
cx: &mut DocContext<'tcx>,
) -> Option<WherePredicate> {
let bound_predicate = predicate.kind();
match bound_predicate.skip_binder() {
- ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => {
- clean_poly_trait_predicate(bound_predicate.rebind(pred), cx)
- }
- ty::PredicateKind::Clause(ty::Clause::RegionOutlives(pred)) => {
- clean_region_outlives_predicate(pred)
- }
- ty::PredicateKind::Clause(ty::Clause::TypeOutlives(pred)) => {
+ ty::ClauseKind::Trait(pred) => clean_poly_trait_predicate(bound_predicate.rebind(pred), cx),
+ ty::ClauseKind::RegionOutlives(pred) => clean_region_outlives_predicate(pred),
+ ty::ClauseKind::TypeOutlives(pred) => {
clean_type_outlives_predicate(bound_predicate.rebind(pred), cx)
}
- ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => {
+ ty::ClauseKind::Projection(pred) => {
Some(clean_projection_predicate(bound_predicate.rebind(pred), cx))
}
// FIXME(generic_const_exprs): should this do something?
- ty::PredicateKind::ConstEvaluatable(..) => None,
- ty::PredicateKind::WellFormed(..) => None,
- ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) => None,
-
- ty::PredicateKind::Subtype(..)
- | ty::PredicateKind::AliasRelate(..)
- | ty::PredicateKind::Coerce(..)
- | ty::PredicateKind::ObjectSafe(..)
- | ty::PredicateKind::ClosureKind(..)
- | ty::PredicateKind::ConstEquate(..)
- | ty::PredicateKind::Ambiguous
- | ty::PredicateKind::TypeWellFormedFromEnv(..) => panic!("not user writable"),
+ ty::ClauseKind::ConstEvaluatable(..)
+ | ty::ClauseKind::WellFormed(..)
+ | ty::ClauseKind::ConstArgHasType(..) => None,
}
}
@@ -370,7 +358,7 @@ fn clean_poly_trait_predicate<'tcx>(
let poly_trait_ref = pred.map_bound(|pred| pred.trait_ref);
Some(WherePredicate::BoundPredicate {
- ty: clean_middle_ty(poly_trait_ref.self_ty(), cx, None),
+ ty: clean_middle_ty(poly_trait_ref.self_ty(), cx, None, None),
bounds: vec![clean_poly_trait_ref_with_bindings(cx, poly_trait_ref, ThinVec::new())],
bound_params: Vec::new(),
})
@@ -396,7 +384,7 @@ fn clean_type_outlives_predicate<'tcx>(
let ty::OutlivesPredicate(ty, lt) = pred.skip_binder();
Some(WherePredicate::BoundPredicate {
- ty: clean_middle_ty(pred.rebind(ty), cx, None),
+ ty: clean_middle_ty(pred.rebind(ty), cx, None, None),
bounds: vec![GenericBound::Outlives(
clean_middle_region(lt).expect("failed to clean lifetimes"),
)],
@@ -409,7 +397,7 @@ fn clean_middle_term<'tcx>(
cx: &mut DocContext<'tcx>,
) -> Term {
match term.skip_binder().unpack() {
- ty::TermKind::Ty(ty) => Term::Type(clean_middle_ty(term.rebind(ty), cx, None)),
+ ty::TermKind::Ty(ty) => Term::Type(clean_middle_ty(term.rebind(ty), cx, None, None)),
ty::TermKind::Const(c) => Term::Constant(clean_middle_const(term.rebind(c), cx)),
}
}
@@ -462,7 +450,7 @@ fn clean_projection<'tcx>(
let trait_ =
clean_trait_ref_with_bindings(cx, ty.map_bound(|ty| ty.trait_ref(cx.tcx)), ThinVec::new());
- let self_type = clean_middle_ty(ty.map_bound(|ty| ty.self_ty()), cx, None);
+ let self_type = clean_middle_ty(ty.map_bound(|ty| ty.self_ty()), cx, None, None);
let self_def_id = if let Some(def_id) = def_id {
cx.tcx.opt_parent(def_id).or(Some(def_id))
} else {
@@ -493,8 +481,13 @@ fn projection_to_path_segment<'tcx>(
PathSegment {
name: item.name,
args: GenericArgs::AngleBracketed {
- args: substs_to_args(cx, ty.map_bound(|ty| &ty.substs[generics.parent_count..]), false)
- .into(),
+ args: substs_to_args(
+ cx,
+ ty.map_bound(|ty| &ty.substs[generics.parent_count..]),
+ false,
+ None,
+ )
+ .into(),
bindings: Default::default(),
},
}
@@ -514,6 +507,7 @@ fn clean_generic_param_def<'tcx>(
ty::Binder::dummy(cx.tcx.type_of(def.def_id).subst_identity()),
cx,
Some(def.def_id),
+ None,
))
} else {
None
@@ -540,6 +534,7 @@ fn clean_generic_param_def<'tcx>(
),
cx,
Some(def.def_id),
+ None,
)),
default: match has_default {
true => Some(Box::new(
@@ -782,10 +777,10 @@ fn clean_ty_generics<'tcx>(
})
.collect::<ThinVec<GenericParamDef>>();
- // param index -> [(trait DefId, associated type name & generics, type, higher-ranked params)]
+ // param index -> [(trait DefId, associated type name & generics, term, higher-ranked params)]
let mut impl_trait_proj = FxHashMap::<
u32,
- Vec<(DefId, PathSegment, ty::Binder<'_, Ty<'_>>, Vec<GenericParamDef>)>,
+ Vec<(DefId, PathSegment, ty::Binder<'_, ty::Term<'_>>, Vec<GenericParamDef>)>,
>::default();
let where_predicates = preds
@@ -796,20 +791,17 @@ fn clean_ty_generics<'tcx>(
let param_idx = (|| {
let bound_p = p.kind();
match bound_p.skip_binder() {
- ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => {
+ ty::ClauseKind::Trait(pred) => {
if let ty::Param(param) = pred.self_ty().kind() {
return Some(param.index);
}
}
- ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate(
- ty,
- _reg,
- ))) => {
+ ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, _reg)) => {
if let ty::Param(param) = ty.kind() {
return Some(param.index);
}
}
- ty::PredicateKind::Clause(ty::Clause::Projection(p)) => {
+ ty::ClauseKind::Projection(p) => {
if let ty::Param(param) = p.projection_ty.self_ty().kind() {
projection = Some(bound_p.rebind(p));
return Some(param.index);
@@ -844,11 +836,10 @@ fn clean_ty_generics<'tcx>(
.as_ref()
.and_then(|(lhs, rhs): &(Type, _)| Some((lhs.projection()?, rhs)))
{
- // FIXME(...): Remove this unwrap()
impl_trait_proj.entry(param_idx).or_default().push((
trait_did,
name,
- rhs.map_bound(|rhs| rhs.ty().unwrap()),
+ *rhs,
p.get_bound_params()
.into_iter()
.flatten()
@@ -871,15 +862,8 @@ fn clean_ty_generics<'tcx>(
let crate::core::ImplTraitParam::ParamIndex(idx) = param else { unreachable!() };
if let Some(proj) = impl_trait_proj.remove(&idx) {
for (trait_did, name, rhs, bound_params) in proj {
- let rhs = clean_middle_ty(rhs, cx, None);
- simplify::merge_bounds(
- cx,
- &mut bounds,
- bound_params,
- trait_did,
- name,
- &Term::Type(rhs),
- );
+ let rhs = clean_middle_term(rhs, cx);
+ simplify::merge_bounds(cx, &mut bounds, bound_params, trait_did, name, &rhs);
}
}
@@ -1111,8 +1095,8 @@ fn clean_fn_decl_with_args<'tcx>(
args: Arguments,
) -> FnDecl {
let output = match decl.output {
- hir::FnRetTy::Return(typ) => Return(clean_ty(typ, cx)),
- hir::FnRetTy::DefaultReturn(..) => DefaultReturn,
+ hir::FnRetTy::Return(typ) => clean_ty(typ, cx),
+ hir::FnRetTy::DefaultReturn(..) => Type::Tuple(Vec::new()),
};
FnDecl { inputs: args, output, c_variadic: decl.c_variadic }
}
@@ -1126,10 +1110,7 @@ fn clean_fn_decl_from_did_and_sig<'tcx>(
// We assume all empty tuples are default return type. This theoretically can discard `-> ()`,
// but shouldn't change any code meaning.
- let output = match clean_middle_ty(sig.output(), cx, None) {
- Type::Tuple(inner) if inner.is_empty() => DefaultReturn,
- ty => Return(ty),
- };
+ let output = clean_middle_ty(sig.output(), cx, None, None);
FnDecl {
output,
@@ -1139,7 +1120,7 @@ fn clean_fn_decl_from_did_and_sig<'tcx>(
.inputs()
.iter()
.map(|t| Argument {
- type_: clean_middle_ty(t.map_bound(|t| *t), cx, None),
+ type_: clean_middle_ty(t.map_bound(|t| *t), cx, None, None),
name: names
.next()
.map(|i| i.name)
@@ -1193,8 +1174,12 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext
hir::TraitItemKind::Type(bounds, Some(default)) => {
let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx));
let bounds = bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect();
- let item_type =
- clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, default)), cx, None);
+ let item_type = clean_middle_ty(
+ ty::Binder::dummy(hir_ty_to_ty(cx.tcx, default)),
+ cx,
+ None,
+ None,
+ );
AssocTypeItem(
Box::new(Typedef {
type_: clean_ty(default, cx),
@@ -1227,14 +1212,18 @@ pub(crate) fn clean_impl_item<'tcx>(
}
hir::ImplItemKind::Fn(ref sig, body) => {
let m = clean_function(cx, sig, impl_.generics, FunctionArgs::Body(body));
- let defaultness = cx.tcx.impl_defaultness(impl_.owner_id);
+ let defaultness = cx.tcx.defaultness(impl_.owner_id);
MethodItem(m, Some(defaultness))
}
hir::ImplItemKind::Type(hir_ty) => {
let type_ = clean_ty(hir_ty, cx);
let generics = clean_generics(impl_.generics, cx);
- let item_type =
- clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), cx, None);
+ let item_type = clean_middle_ty(
+ ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)),
+ cx,
+ None,
+ None,
+ );
AssocTypeItem(
Box::new(Typedef { type_, generics, item_type: Some(item_type) }),
Vec::new(),
@@ -1257,11 +1246,12 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
ty::Binder::dummy(tcx.type_of(assoc_item.def_id).subst_identity()),
cx,
Some(assoc_item.def_id),
+ None,
);
let provided = match assoc_item.container {
ty::ImplContainer => true,
- ty::TraitContainer => tcx.impl_defaultness(assoc_item.def_id).has_value(),
+ ty::TraitContainer => tcx.defaultness(assoc_item.def_id).has_value(),
};
if provided {
AssocConstItem(ty, ConstantKind::Extern { def_id: assoc_item.def_id })
@@ -1346,19 +1336,51 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
}
}
+ let mut predicates = tcx.explicit_predicates_of(assoc_item.def_id).predicates;
if let ty::TraitContainer = assoc_item.container {
let bounds =
tcx.explicit_item_bounds(assoc_item.def_id).subst_identity_iter_copied();
- let predicates = tcx.explicit_predicates_of(assoc_item.def_id).predicates;
- let predicates =
- tcx.arena.alloc_from_iter(bounds.chain(predicates.iter().copied()));
- let mut generics = clean_ty_generics(
- cx,
- tcx.generics_of(assoc_item.def_id),
- ty::GenericPredicates { parent: None, predicates },
- );
- // Filter out the bounds that are (likely?) directly attached to the associated type,
- // as opposed to being located in the where clause.
+ predicates = tcx.arena.alloc_from_iter(bounds.chain(predicates.iter().copied()));
+ }
+ let mut generics = clean_ty_generics(
+ cx,
+ tcx.generics_of(assoc_item.def_id),
+ ty::GenericPredicates { parent: None, predicates },
+ );
+ // Move bounds that are (likely) directly attached to the parameters of the
+ // (generic) associated type from the where clause to the respective parameter.
+ // There is no guarantee that this is what the user actually wrote but we have
+ // no way of knowing.
+ let mut where_predicates = ThinVec::new();
+ for mut pred in generics.where_predicates {
+ if let WherePredicate::BoundPredicate { ty: Generic(arg), bounds, .. } = &mut pred
+ && let Some(GenericParamDef {
+ kind: GenericParamDefKind::Type { bounds: param_bounds, .. },
+ ..
+ }) = generics.params.iter_mut().find(|param| &param.name == arg)
+ {
+ param_bounds.append(bounds);
+ } else if let WherePredicate::RegionPredicate { lifetime: Lifetime(arg), bounds } = &mut pred
+ && let Some(GenericParamDef {
+ kind: GenericParamDefKind::Lifetime { outlives: param_bounds },
+ ..
+ }) = generics.params.iter_mut().find(|param| &param.name == arg)
+ {
+ param_bounds.extend(bounds.drain(..).map(|bound| match bound {
+ GenericBound::Outlives(lifetime) => lifetime,
+ _ => unreachable!(),
+ }));
+ } else {
+ where_predicates.push(pred);
+ }
+ }
+ generics.where_predicates = where_predicates;
+
+ if let ty::TraitContainer = assoc_item.container {
+ // Move bounds that are (likely) directly attached to the associated type
+ // from the where-clause to the associated type.
+ // There is no guarantee that this is what the user actually wrote but we have
+ // no way of knowing.
let mut bounds: Vec<GenericBound> = Vec::new();
generics.where_predicates.retain_mut(|pred| match *pred {
WherePredicate::BoundPredicate {
@@ -1415,44 +1437,17 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
}
None => bounds.push(GenericBound::maybe_sized(cx)),
}
- // Move bounds that are (likely) directly attached to the parameters of the
- // (generic) associated type from the where clause to the respective parameter.
- // There is no guarantee that this is what the user actually wrote but we have
- // no way of knowing.
- let mut where_predicates = ThinVec::new();
- for mut pred in generics.where_predicates {
- if let WherePredicate::BoundPredicate { ty: Generic(arg), bounds, .. } = &mut pred
- && let Some(GenericParamDef {
- kind: GenericParamDefKind::Type { bounds: param_bounds, .. },
- ..
- }) = generics.params.iter_mut().find(|param| &param.name == arg)
- {
- param_bounds.append(bounds);
- } else if let WherePredicate::RegionPredicate { lifetime: Lifetime(arg), bounds } = &mut pred
- && let Some(GenericParamDef {
- kind: GenericParamDefKind::Lifetime { outlives: param_bounds },
- ..
- }) = generics.params.iter_mut().find(|param| &param.name == arg) {
- param_bounds.extend(bounds.drain(..).map(|bound| match bound {
- GenericBound::Outlives(lifetime) => lifetime,
- _ => unreachable!(),
- }));
- } else {
- where_predicates.push(pred);
- }
- }
- generics.where_predicates = where_predicates;
- if tcx.impl_defaultness(assoc_item.def_id).has_value() {
+ if tcx.defaultness(assoc_item.def_id).has_value() {
AssocTypeItem(
Box::new(Typedef {
type_: clean_middle_ty(
ty::Binder::dummy(tcx.type_of(assoc_item.def_id).subst_identity()),
cx,
Some(assoc_item.def_id),
+ None,
),
generics,
- // FIXME: should we obtain the Type from HIR and pass it on here?
item_type: None,
}),
bounds,
@@ -1461,20 +1456,19 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
TyAssocTypeItem(generics, bounds)
}
} else {
- // FIXME: when could this happen? Associated items in inherent impls?
AssocTypeItem(
Box::new(Typedef {
type_: clean_middle_ty(
ty::Binder::dummy(tcx.type_of(assoc_item.def_id).subst_identity()),
cx,
Some(assoc_item.def_id),
+ None,
),
- generics: Generics {
- params: ThinVec::new(),
- where_predicates: ThinVec::new(),
- },
+ generics,
item_type: None,
}),
+ // Associated types inside trait or inherent impls are not allowed to have
+ // item bounds. Thus we don't attempt to move any bounds there.
Vec::new(),
)
}
@@ -1513,7 +1507,7 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type
if !ty.has_escaping_bound_vars()
&& let Some(normalized_value) = normalize(cx, ty::Binder::dummy(ty))
{
- return clean_middle_ty(normalized_value, cx, None);
+ return clean_middle_ty(normalized_value, cx, None, None);
}
let trait_segments = &p.segments[..p.segments.len() - 1];
@@ -1741,11 +1735,153 @@ fn normalize<'tcx>(
}
}
+fn clean_trait_object_lifetime_bound<'tcx>(
+ region: ty::Region<'tcx>,
+ container: Option<ContainerTy<'tcx>>,
+ preds: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
+ tcx: TyCtxt<'tcx>,
+) -> Option<Lifetime> {
+ if can_elide_trait_object_lifetime_bound(region, container, preds, tcx) {
+ return None;
+ }
+
+ // Since there is a semantic difference between an implicitly elided (i.e. "defaulted") object
+ // lifetime and an explicitly elided object lifetime (`'_`), we intentionally don't hide the
+ // latter contrary to `clean_middle_region`.
+ match *region {
+ ty::ReStatic => Some(Lifetime::statik()),
+ ty::ReEarlyBound(region) if region.name != kw::Empty => Some(Lifetime(region.name)),
+ ty::ReLateBound(_, ty::BoundRegion { kind: ty::BrNamed(_, name), .. })
+ if name != kw::Empty =>
+ {
+ Some(Lifetime(name))
+ }
+ ty::ReEarlyBound(_)
+ | ty::ReLateBound(..)
+ | ty::ReFree(_)
+ | ty::ReVar(_)
+ | ty::RePlaceholder(_)
+ | ty::ReErased
+ | ty::ReError(_) => None,
+ }
+}
+
+fn can_elide_trait_object_lifetime_bound<'tcx>(
+ region: ty::Region<'tcx>,
+ container: Option<ContainerTy<'tcx>>,
+ preds: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
+ tcx: TyCtxt<'tcx>,
+) -> bool {
+ // Below we quote extracts from https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes
+
+ // > If the trait object is used as a type argument of a generic type then the containing type is
+ // > first used to try to infer a bound.
+ let default = container
+ .map_or(ObjectLifetimeDefault::Empty, |container| container.object_lifetime_default(tcx));
+
+ // > If there is a unique bound from the containing type then that is the default
+ // If there is a default object lifetime and the given region is lexically equal to it, elide it.
+ match default {
+ ObjectLifetimeDefault::Static => return *region == ty::ReStatic,
+ // FIXME(fmease): Don't compare lexically but respect de Bruijn indices etc. to handle shadowing correctly.
+ ObjectLifetimeDefault::Arg(default) => return region.get_name() == default.get_name(),
+ // > If there is more than one bound from the containing type then an explicit bound must be specified
+ // Due to ambiguity there is no default trait-object lifetime and thus elision is impossible.
+ // Don't elide the lifetime.
+ ObjectLifetimeDefault::Ambiguous => return false,
+ // There is no meaningful bound. Further processing is needed...
+ ObjectLifetimeDefault::Empty => {}
+ }
+
+ // > If neither of those rules apply, then the bounds on the trait are used:
+ match *object_region_bounds(tcx, preds) {
+ // > If the trait has no lifetime bounds, then the lifetime is inferred in expressions
+ // > and is 'static outside of expressions.
+ // FIXME: If we are in an expression context (i.e. fn bodies and const exprs) then the default is
+ // `'_` and not `'static`. Only if we are in a non-expression one, the default is `'static`.
+ // Note however that at the time of this writing it should be fine to disregard this subtlety
+ // as we neither render const exprs faithfully anyway (hiding them in some places or using `_` instead)
+ // nor show the contents of fn bodies.
+ [] => *region == ty::ReStatic,
+ // > If the trait is defined with a single lifetime bound then that bound is used.
+ // > If 'static is used for any lifetime bound then 'static is used.
+ // FIXME(fmease): Don't compare lexically but respect de Bruijn indices etc. to handle shadowing correctly.
+ [object_region] => object_region.get_name() == region.get_name(),
+ // There are several distinct trait regions and none are `'static`.
+ // Due to ambiguity there is no default trait-object lifetime and thus elision is impossible.
+ // Don't elide the lifetime.
+ _ => false,
+ }
+}
+
+#[derive(Debug)]
+pub(crate) enum ContainerTy<'tcx> {
+ Ref(ty::Region<'tcx>),
+ Regular {
+ ty: DefId,
+ args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>,
+ has_self: bool,
+ arg: usize,
+ },
+}
+
+impl<'tcx> ContainerTy<'tcx> {
+ fn object_lifetime_default(self, tcx: TyCtxt<'tcx>) -> ObjectLifetimeDefault<'tcx> {
+ match self {
+ Self::Ref(region) => ObjectLifetimeDefault::Arg(region),
+ Self::Regular { ty: container, args, has_self, arg: index } => {
+ let (DefKind::Struct
+ | DefKind::Union
+ | DefKind::Enum
+ | DefKind::TyAlias { .. }
+ | DefKind::Trait) = tcx.def_kind(container)
+ else {
+ return ObjectLifetimeDefault::Empty;
+ };
+
+ let generics = tcx.generics_of(container);
+ debug_assert_eq!(generics.parent_count, 0);
+
+ // If the container is a trait object type, the arguments won't contain the self type but the
+ // generics of the corresponding trait will. In such a case, offset the index by one.
+ // For comparison, if the container is a trait inside a bound, the arguments do contain the
+ // self type.
+ let offset =
+ if !has_self && generics.parent.is_none() && generics.has_self { 1 } else { 0 };
+ let param = generics.params[index + offset].def_id;
+
+ let default = tcx.object_lifetime_default(param);
+ match default {
+ rbv::ObjectLifetimeDefault::Param(lifetime) => {
+ // The index is relative to the parent generics but since we don't have any,
+ // we don't need to translate it.
+ let index = generics.param_def_id_to_index[&lifetime];
+ let arg = args.skip_binder()[index as usize].expect_region();
+ ObjectLifetimeDefault::Arg(arg)
+ }
+ rbv::ObjectLifetimeDefault::Empty => ObjectLifetimeDefault::Empty,
+ rbv::ObjectLifetimeDefault::Static => ObjectLifetimeDefault::Static,
+ rbv::ObjectLifetimeDefault::Ambiguous => ObjectLifetimeDefault::Ambiguous,
+ }
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum ObjectLifetimeDefault<'tcx> {
+ Empty,
+ Static,
+ Ambiguous,
+ Arg(ty::Region<'tcx>),
+}
+
#[instrument(level = "trace", skip(cx), ret)]
pub(crate) fn clean_middle_ty<'tcx>(
bound_ty: ty::Binder<'tcx, Ty<'tcx>>,
cx: &mut DocContext<'tcx>,
parent_def_id: Option<DefId>,
+ container: Option<ContainerTy<'tcx>>,
) -> Type {
let bound_ty = normalize(cx, bound_ty).unwrap_or(bound_ty);
match *bound_ty.skip_binder().kind() {
@@ -1756,19 +1892,24 @@ pub(crate) fn clean_middle_ty<'tcx>(
ty::Uint(uint_ty) => Primitive(uint_ty.into()),
ty::Float(float_ty) => Primitive(float_ty.into()),
ty::Str => Primitive(PrimitiveType::Str),
- ty::Slice(ty) => Slice(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None))),
+ ty::Slice(ty) => Slice(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None))),
ty::Array(ty, mut n) => {
n = n.eval(cx.tcx, ty::ParamEnv::reveal_all());
let n = print_const(cx, n);
- Array(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None)), n.into())
+ Array(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None)), n.into())
}
ty::RawPtr(mt) => {
- RawPointer(mt.mutbl, Box::new(clean_middle_ty(bound_ty.rebind(mt.ty), cx, None)))
+ RawPointer(mt.mutbl, Box::new(clean_middle_ty(bound_ty.rebind(mt.ty), cx, None, None)))
}
ty::Ref(r, ty, mutbl) => BorrowedRef {
lifetime: clean_middle_region(r),
mutability: mutbl,
- type_: Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None)),
+ type_: Box::new(clean_middle_ty(
+ bound_ty.rebind(ty),
+ cx,
+ None,
+ Some(ContainerTy::Ref(r)),
+ )),
},
ty::FnDef(..) | ty::FnPtr(_) => {
// FIXME: should we merge the outer and inner binders somehow?
@@ -1820,10 +1961,8 @@ pub(crate) fn clean_middle_ty<'tcx>(
inline::record_extern_fqn(cx, did, ItemType::Trait);
- // FIXME(fmease): Hide the trait-object lifetime bound if it coincides with its default
- // to partially address #44306. Follow the rules outlined at
- // https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes
- let lifetime = clean_middle_region(*reg);
+ let lifetime = clean_trait_object_lifetime_bound(*reg, container, obj, cx.tcx);
+
let mut bounds = dids
.map(|did| {
let empty = ty::Binder::dummy(InternalSubsts::empty());
@@ -1872,16 +2011,16 @@ pub(crate) fn clean_middle_ty<'tcx>(
DynTrait(bounds, lifetime)
}
ty::Tuple(t) => {
- Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None)).collect())
+ Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None, None)).collect())
}
- ty::Alias(ty::Projection, ref data) => {
- clean_projection(bound_ty.rebind(*data), cx, parent_def_id)
+ ty::Alias(ty::Projection, data) => {
+ clean_projection(bound_ty.rebind(data), cx, parent_def_id)
}
ty::Alias(ty::Inherent, alias_ty) => {
let alias_ty = bound_ty.rebind(alias_ty);
- let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None);
+ let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None, None);
Type::QPath(Box::new(QPathData {
assoc: PathSegment {
@@ -1891,6 +2030,7 @@ pub(crate) fn clean_middle_ty<'tcx>(
cx,
alias_ty.map_bound(|ty| ty.substs.as_slice()),
true,
+ None,
)
.into(),
bindings: Default::default(),
@@ -1902,6 +2042,24 @@ pub(crate) fn clean_middle_ty<'tcx>(
}))
}
+ ty::Alias(ty::Weak, data) => {
+ if cx.tcx.features().lazy_type_alias {
+ // Weak type alias `data` represents the `type X` in `type X = Y`. If we need `Y`,
+ // we need to use `type_of`.
+ let path = external_path(
+ cx,
+ data.def_id,
+ false,
+ ThinVec::new(),
+ bound_ty.rebind(data.substs),
+ );
+ Type::Path { path }
+ } else {
+ let ty = cx.tcx.type_of(data.def_id).subst(cx.tcx, data.substs);
+ clean_middle_ty(bound_ty.rebind(ty), cx, None, None)
+ }
+ }
+
ty::Param(ref p) => {
if let Some(bounds) = cx.impl_trait_bounds.remove(&p.index.into()) {
ImplTrait(bounds)
@@ -1950,7 +2108,7 @@ pub(crate) fn clean_middle_ty<'tcx>(
fn clean_middle_opaque_bounds<'tcx>(
cx: &mut DocContext<'tcx>,
- bounds: Vec<ty::Predicate<'tcx>>,
+ bounds: Vec<ty::Clause<'tcx>>,
) -> Type {
let mut regions = vec![];
let mut has_sized = false;
@@ -1959,13 +2117,8 @@ fn clean_middle_opaque_bounds<'tcx>(
.filter_map(|bound| {
let bound_predicate = bound.kind();
let trait_ref = match bound_predicate.skip_binder() {
- ty::PredicateKind::Clause(ty::Clause::Trait(tr)) => {
- bound_predicate.rebind(tr.trait_ref)
- }
- ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate(
- _ty,
- reg,
- ))) => {
+ ty::ClauseKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref),
+ ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => {
if let Some(r) = clean_middle_region(reg) {
regions.push(GenericBound::Outlives(r));
}
@@ -1982,9 +2135,7 @@ fn clean_middle_opaque_bounds<'tcx>(
let bindings: ThinVec<_> = bounds
.iter()
.filter_map(|bound| {
- if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) =
- bound.kind().skip_binder()
- {
+ if let ty::ClauseKind::Projection(proj) = bound.kind().skip_binder() {
if proj.projection_ty.trait_ref(cx.tcx) == trait_ref.skip_binder() {
Some(TypeBinding {
assoc: projection_to_path_segment(
@@ -2026,6 +2177,7 @@ pub(crate) fn clean_middle_field<'tcx>(field: &ty::FieldDef, cx: &mut DocContext
ty::Binder::dummy(cx.tcx.type_of(field.did).subst_identity()),
cx,
Some(field.did),
+ None,
),
cx,
)
@@ -2183,7 +2335,8 @@ fn get_all_import_attributes<'hir>(
// This is the "original" reexport so we get all its attributes without filtering them.
attrs = import_attrs.iter().map(|attr| (Cow::Borrowed(attr), Some(def_id))).collect();
first = false;
- } else {
+ // We don't add attributes of an intermediate re-export if it has `#[doc(hidden)]`.
+ } else if !cx.tcx.is_doc_hidden(def_id) {
add_without_unwanted_attributes(&mut attrs, import_attrs, is_inline, Some(def_id));
}
}
@@ -2213,7 +2366,7 @@ fn filter_tokens_from_list(
tokens
}
-/// When inlining items, we merge its attributes (and all the reexports attributes too) with the
+/// When inlining items, we merge their attributes (and all the reexports attributes too) with the
/// final reexport. For example:
///
/// ```ignore (just an example)
@@ -2316,7 +2469,12 @@ fn clean_maybe_renamed_item<'tcx>(
ItemKind::TyAlias(hir_ty, generics) => {
*cx.current_type_aliases.entry(def_id).or_insert(0) += 1;
let rustdoc_ty = clean_ty(hir_ty, cx);
- let ty = clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), cx, None);
+ let ty = clean_middle_ty(
+ ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)),
+ cx,
+ None,
+ None,
+ );
let generics = clean_generics(generics, cx);
if let Some(count) = cx.current_type_aliases.get_mut(&def_id) {
*count -= 1;
@@ -2420,6 +2578,7 @@ fn clean_impl<'tcx>(
ty::Binder::dummy(tcx.type_of(def_id).subst_identity()),
cx,
Some(def_id.to_def_id()),
+ None,
)),
_ => None,
});
diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs
index 3c72b0bf9..65b1b72ad 100644
--- a/src/librustdoc/clean/simplify.rs
+++ b/src/librustdoc/clean/simplify.rs
@@ -128,7 +128,7 @@ fn trait_is_same_or_supertrait(cx: &DocContext<'_>, child: DefId, trait_: DefId)
.predicates
.iter()
.filter_map(|(pred, _)| {
- if let ty::PredicateKind::Clause(ty::Clause::Trait(pred)) = pred.kind().skip_binder() {
+ if let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder() {
if pred.trait_ref.self_ty() == self_ty { Some(pred.def_id()) } else { None }
} else {
None
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index e9ccea2cf..26139d527 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -42,7 +42,6 @@ use crate::formats::item_type::ItemType;
use crate::html::render::Context;
use crate::passes::collect_intra_doc_links::UrlFragment;
-pub(crate) use self::FnRetTy::*;
pub(crate) use self::ItemKind::*;
pub(crate) use self::SelfTy::*;
pub(crate) use self::Type::{
@@ -359,15 +358,15 @@ fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
impl Item {
pub(crate) fn stability<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option<Stability> {
- self.item_id.as_def_id().and_then(|did| tcx.lookup_stability(did))
+ self.def_id().and_then(|did| tcx.lookup_stability(did))
}
pub(crate) fn const_stability<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option<ConstStability> {
- self.item_id.as_def_id().and_then(|did| tcx.lookup_const_stability(did))
+ self.def_id().and_then(|did| tcx.lookup_const_stability(did))
}
pub(crate) fn deprecation(&self, tcx: TyCtxt<'_>) -> Option<Deprecation> {
- self.item_id.as_def_id().and_then(|did| tcx.lookup_deprecation(did))
+ self.def_id().and_then(|did| tcx.lookup_deprecation(did))
}
pub(crate) fn inner_docs(&self, tcx: TyCtxt<'_>) -> bool {
@@ -392,7 +391,7 @@ impl Item {
panic!("blanket impl item has non-blanket ID")
}
}
- _ => self.item_id.as_def_id().map(|did| rustc_span(did, tcx)),
+ _ => self.def_id().map(|did| rustc_span(did, tcx)),
}
}
@@ -502,7 +501,7 @@ impl Item {
}
pub(crate) fn is_crate(&self) -> bool {
- self.is_mod() && self.item_id.as_def_id().map_or(false, |did| did.is_crate_root())
+ self.is_mod() && self.def_id().map_or(false, |did| did.is_crate_root())
}
pub(crate) fn is_mod(&self) -> bool {
self.type_() == ItemType::Module
@@ -639,11 +638,11 @@ impl Item {
}
let header = match *self.kind {
ItemKind::ForeignFunctionItem(_) => {
- let def_id = self.item_id.as_def_id().unwrap();
+ let def_id = self.def_id().unwrap();
let abi = tcx.fn_sig(def_id).skip_binder().abi();
hir::FnHeader {
unsafety: if abi == Abi::RustIntrinsic {
- intrinsic_operation_unsafety(tcx, self.item_id.as_def_id().unwrap())
+ intrinsic_operation_unsafety(tcx, self.def_id().unwrap())
} else {
hir::Unsafety::Unsafe
},
@@ -660,7 +659,7 @@ impl Item {
}
}
ItemKind::FunctionItem(_) | ItemKind::MethodItem(_, _) | ItemKind::TyMethodItem(_) => {
- let def_id = self.item_id.as_def_id().unwrap();
+ let def_id = self.def_id().unwrap();
build_fn_header(def_id, tcx, tcx.asyncness(def_id))
}
_ => return None,
@@ -739,7 +738,7 @@ impl Item {
}
})
.collect();
- if let Some(def_id) = self.item_id.as_def_id() &&
+ if let Some(def_id) = self.def_id() &&
!def_id.is_local() &&
// This check is needed because `adt_def` will panic if not a compatible type otherwise...
matches!(self.type_(), ItemType::Struct | ItemType::Enum | ItemType::Union)
@@ -784,6 +783,14 @@ impl Item {
}
attrs
}
+
+ pub fn is_doc_hidden(&self) -> bool {
+ self.attrs.is_doc_hidden()
+ }
+
+ pub fn def_id(&self) -> Option<DefId> {
+ self.item_id.as_def_id()
+ }
}
#[derive(Clone, Debug)]
@@ -949,6 +956,8 @@ pub(crate) trait AttributesExt {
.filter_map(|attr| Cfg::parse(attr.meta_item()?).ok())
.fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg)
} else if doc_auto_cfg_active {
+ // If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because
+ // `doc(cfg())` overrides `cfg()`).
self.iter()
.filter(|attr| attr.has_name(sym::cfg))
.filter_map(|attr| single(attr.meta_item_list()?))
@@ -1130,6 +1139,10 @@ impl Attributes {
false
}
+ fn is_doc_hidden(&self) -> bool {
+ self.has_doc_flag(sym::hidden)
+ }
+
pub(crate) fn from_ast(attrs: &[ast::Attribute]) -> Attributes {
Attributes::from_ast_iter(attrs.iter().map(|attr| (attr, None)), false)
}
@@ -1353,7 +1366,7 @@ pub(crate) struct Function {
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub(crate) struct FnDecl {
pub(crate) inputs: Arguments,
- pub(crate) output: FnRetTy,
+ pub(crate) output: Type,
pub(crate) c_variadic: bool,
}
@@ -1371,18 +1384,16 @@ impl FnDecl {
///
/// This function will panic if the return type does not match the expected sugaring for async
/// functions.
- pub(crate) fn sugared_async_return_type(&self) -> FnRetTy {
- match &self.output {
- FnRetTy::Return(Type::ImplTrait(bounds)) => match &bounds[0] {
- GenericBound::TraitBound(PolyTrait { trait_, .. }, ..) => {
- let bindings = trait_.bindings().unwrap();
- let ret_ty = bindings[0].term();
- let ty = ret_ty.ty().expect("Unexpected constant return term");
- FnRetTy::Return(ty.clone())
- }
- _ => panic!("unexpected desugaring of async function"),
- },
- _ => panic!("unexpected desugaring of async function"),
+ pub(crate) fn sugared_async_return_type(&self) -> Type {
+ if let Type::ImplTrait(v) = &self.output &&
+ let [GenericBound::TraitBound(PolyTrait { trait_, .. }, _ )] = &v[..]
+ {
+ let bindings = trait_.bindings().unwrap();
+ let ret_ty = bindings[0].term();
+ let ty = ret_ty.ty().expect("Unexpected constant return term");
+ ty.clone()
+ } else {
+ panic!("unexpected desugaring of async function")
}
}
}
@@ -1425,21 +1436,6 @@ impl Argument {
}
}
-#[derive(Clone, PartialEq, Eq, Debug, Hash)]
-pub(crate) enum FnRetTy {
- Return(Type),
- DefaultReturn,
-}
-
-impl FnRetTy {
- pub(crate) fn as_return(&self) -> Option<&Type> {
- match self {
- Return(ret) => Some(ret),
- DefaultReturn => None,
- }
- }
-}
-
#[derive(Clone, Debug)]
pub(crate) struct Trait {
pub(crate) def_id: DefId,
@@ -1641,6 +1637,10 @@ impl Type {
matches!(self, Type::ImplTrait(_))
}
+ pub(crate) fn is_unit(&self) -> bool {
+ matches!(self, Type::Tuple(v) if v.is_empty())
+ }
+
pub(crate) fn projection(&self) -> Option<(&Type, DefId, PathSegment)> {
if let QPath(box QPathData { self_type, trait_, assoc, .. }) = self {
Some((self_type, trait_.as_ref()?.def_id(), assoc.clone()))
@@ -2389,6 +2389,7 @@ impl ImplKind {
#[derive(Clone, Debug)]
pub(crate) struct Import {
pub(crate) kind: ImportKind,
+ /// The item being re-exported.
pub(crate) source: ImportSource,
pub(crate) should_be_displayed: bool,
}
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index 366f93952..b786ecbe3 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -21,6 +21,7 @@ use rustc_middle::ty::{self, TyCtxt};
use rustc_span::symbol::{kw, sym, Symbol};
use std::fmt::Write as _;
use std::mem;
+use std::sync::LazyLock as Lazy;
use thin_vec::{thin_vec, ThinVec};
#[cfg(test)]
@@ -53,8 +54,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
let primitives = local_crate.primitives(cx.tcx);
let keywords = local_crate.keywords(cx.tcx);
{
- let ItemKind::ModuleItem(ref mut m) = *module.kind
- else { unreachable!() };
+ let ItemKind::ModuleItem(ref mut m) = *module.kind else { unreachable!() };
m.items.extend(primitives.iter().map(|&(def_id, prim)| {
Item::from_def_id_and_parts(
def_id,
@@ -73,28 +73,37 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
pub(crate) fn substs_to_args<'tcx>(
cx: &mut DocContext<'tcx>,
- substs: ty::Binder<'tcx, &[ty::subst::GenericArg<'tcx>]>,
- mut skip_first: bool,
+ args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>,
+ has_self: bool,
+ container: Option<DefId>,
) -> Vec<GenericArg> {
+ let mut skip_first = has_self;
let mut ret_val =
- Vec::with_capacity(substs.skip_binder().len().saturating_sub(if skip_first {
- 1
- } else {
- 0
- }));
- ret_val.extend(substs.iter().filter_map(|kind| match kind.skip_binder().unpack() {
- GenericArgKind::Lifetime(lt) => {
- Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
- }
- GenericArgKind::Type(_) if skip_first => {
- skip_first = false;
- None
- }
- GenericArgKind::Type(ty) => {
- Some(GenericArg::Type(clean_middle_ty(kind.rebind(ty), cx, None)))
- }
- GenericArgKind::Const(ct) => {
- Some(GenericArg::Const(Box::new(clean_middle_const(kind.rebind(ct), cx))))
+ Vec::with_capacity(args.skip_binder().len().saturating_sub(if skip_first { 1 } else { 0 }));
+
+ ret_val.extend(args.iter().enumerate().filter_map(|(index, kind)| {
+ match kind.skip_binder().unpack() {
+ GenericArgKind::Lifetime(lt) => {
+ Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
+ }
+ GenericArgKind::Type(_) if skip_first => {
+ skip_first = false;
+ None
+ }
+ GenericArgKind::Type(ty) => Some(GenericArg::Type(clean_middle_ty(
+ kind.rebind(ty),
+ cx,
+ None,
+ container.map(|container| crate::clean::ContainerTy::Regular {
+ ty: container,
+ args,
+ has_self,
+ arg: index,
+ }),
+ ))),
+ GenericArgKind::Const(ct) => {
+ Some(GenericArg::Const(Box::new(clean_middle_const(kind.rebind(ct), cx))))
+ }
}
}));
ret_val
@@ -107,7 +116,7 @@ fn external_generic_args<'tcx>(
bindings: ThinVec<TypeBinding>,
substs: ty::Binder<'tcx, SubstsRef<'tcx>>,
) -> GenericArgs {
- let args = substs_to_args(cx, substs.map_bound(|substs| &substs[..]), has_self);
+ let args = substs_to_args(cx, substs.map_bound(|substs| &substs[..]), has_self, Some(did));
if cx.tcx.fn_trait_kind_from_def_id(did).is_some() {
let ty = substs
@@ -118,7 +127,7 @@ fn external_generic_args<'tcx>(
let inputs =
// The trait's first substitution is the one after self, if there is one.
match ty.skip_binder().kind() {
- ty::Tuple(tys) => tys.iter().map(|t| clean_middle_ty(ty.rebind(t), cx, None)).collect::<Vec<_>>().into(),
+ ty::Tuple(tys) => tys.iter().map(|t| clean_middle_ty(ty.rebind(t), cx, None, None)).collect::<Vec<_>>().into(),
_ => return GenericArgs::AngleBracketed { args: args.into(), bindings },
};
let output = bindings.into_iter().next().and_then(|binding| match binding.kind {
@@ -571,6 +580,8 @@ pub(crate) fn has_doc_flag(tcx: TyCtxt<'_>, did: DefId, flag: Symbol) -> bool {
///
/// Set by `bootstrap::Builder::doc_rust_lang_org_channel` in order to keep tests passing on beta/stable.
pub(crate) const DOC_RUST_LANG_ORG_CHANNEL: &str = env!("DOC_RUST_LANG_ORG_CHANNEL");
+pub(crate) static DOC_CHANNEL: Lazy<&'static str> =
+ Lazy::new(|| DOC_RUST_LANG_ORG_CHANNEL.rsplit("/").filter(|c| !c.is_empty()).next().unwrap());
/// Render a sequence of macro arms in a format suitable for displaying to the user
/// as part of an item declaration.
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index 9f08609a6..217f1a6ee 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -15,6 +15,7 @@ use rustc_session::config::{
use rustc_session::getopts;
use rustc_session::lint::Level;
use rustc_session::search_paths::SearchPath;
+use rustc_session::EarlyErrorHandler;
use rustc_span::edition::Edition;
use rustc_target::spec::TargetTriple;
@@ -311,32 +312,33 @@ impl Options {
/// Parses the given command-line for options. If an error message or other early-return has
/// been printed, returns `Err` with the exit code.
pub(crate) fn from_matches(
+ handler: &mut EarlyErrorHandler,
matches: &getopts::Matches,
args: Vec<String>,
) -> Result<(Options, RenderOptions), i32> {
// Check for unstable options.
- nightly_options::check_nightly_options(matches, &opts());
+ nightly_options::check_nightly_options(handler, matches, &opts());
if args.is_empty() || matches.opt_present("h") || matches.opt_present("help") {
crate::usage("rustdoc");
return Err(0);
} else if matches.opt_present("version") {
- rustc_driver::version!("rustdoc", matches);
+ rustc_driver::version!(&handler, "rustdoc", matches);
return Err(0);
}
- if rustc_driver::describe_flag_categories(&matches) {
+ if rustc_driver::describe_flag_categories(handler, &matches) {
return Err(0);
}
- let color = config::parse_color(matches);
+ let color = config::parse_color(handler, matches);
let config::JsonConfig { json_rendered, json_unused_externs, .. } =
- config::parse_json(matches);
- let error_format = config::parse_error_format(matches, color, json_rendered);
+ config::parse_json(handler, matches);
+ let error_format = config::parse_error_format(handler, matches, color, json_rendered);
let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_default();
- let codegen_options = CodegenOptions::build(matches, error_format);
- let unstable_opts = UnstableOptions::build(matches, error_format);
+ let codegen_options = CodegenOptions::build(handler, matches);
+ let unstable_opts = UnstableOptions::build(handler, matches);
let diag = new_handler(error_format, None, diagnostic_width, &unstable_opts);
@@ -393,8 +395,7 @@ impl Options {
&& !matches.opt_present("show-coverage")
&& !nightly_options::is_unstable_enabled(matches)
{
- rustc_session::early_error(
- error_format,
+ handler.early_error(
"the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/76578)",
);
}
@@ -432,7 +433,7 @@ impl Options {
return Err(0);
}
- let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
+ let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(handler, matches);
let input = PathBuf::from(if describe_lints {
"" // dummy, this won't be used
@@ -446,12 +447,9 @@ impl Options {
&matches.free[0]
});
- let libs = matches
- .opt_strs("L")
- .iter()
- .map(|s| SearchPath::from_cli_opt(s, error_format))
- .collect();
- let externs = parse_externs(matches, &unstable_opts, error_format);
+ let libs =
+ matches.opt_strs("L").iter().map(|s| SearchPath::from_cli_opt(handler, s)).collect();
+ let externs = parse_externs(handler, matches, &unstable_opts);
let extern_html_root_urls = match parse_extern_html_roots(matches) {
Ok(ex) => ex,
Err(err) => {
@@ -589,7 +587,7 @@ impl Options {
}
}
- let edition = config::parse_crate_edition(matches);
+ let edition = config::parse_crate_edition(handler, matches);
let mut id_map = html::markdown::IdMap::new();
let Some(external_html) = ExternalHtml::load(
@@ -623,7 +621,7 @@ impl Options {
}
}
- let target = parse_target_triple(matches, error_format);
+ let target = parse_target_triple(handler, matches);
let show_coverage = matches.opt_present("show-coverage");
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index e10a62977..9687b8b18 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -13,8 +13,8 @@ use rustc_interface::interface;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
use rustc_session::config::{self, CrateType, ErrorOutputType, ResolveDocLinks};
-use rustc_session::lint;
use rustc_session::Session;
+use rustc_session::{lint, EarlyErrorHandler};
use rustc_span::symbol::sym;
use rustc_span::{source_map, Span};
@@ -181,6 +181,7 @@ pub(crate) fn new_handler(
/// Parse, resolve, and typecheck the given crate.
pub(crate) fn create_config(
+ handler: &EarlyErrorHandler,
RustdocOptions {
input,
crate_name,
@@ -258,8 +259,8 @@ pub(crate) fn create_config(
interface::Config {
opts: sessopts,
- crate_cfg: interface::parse_cfgspecs(cfgs),
- crate_check_cfg: interface::parse_check_cfg(check_cfgs),
+ crate_cfg: interface::parse_cfgspecs(handler, cfgs),
+ crate_check_cfg: interface::parse_check_cfg(handler, check_cfgs),
input,
output_file: None,
output_dir: None,
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index f6631b66f..217257316 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -12,7 +12,7 @@ use rustc_parse::maybe_new_parser_from_source_str;
use rustc_parse::parser::attr::InnerAttrPolicy;
use rustc_session::config::{self, CrateType, ErrorOutputType};
use rustc_session::parse::ParseSess;
-use rustc_session::{lint, Session};
+use rustc_session::{lint, EarlyErrorHandler, Session};
use rustc_span::edition::Edition;
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::sym;
@@ -85,13 +85,18 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
..config::Options::default()
};
+ let early_error_handler = EarlyErrorHandler::new(ErrorOutputType::default());
+
let mut cfgs = options.cfgs.clone();
cfgs.push("doc".to_owned());
cfgs.push("doctest".to_owned());
let config = interface::Config {
opts: sessopts,
- crate_cfg: interface::parse_cfgspecs(cfgs),
- crate_check_cfg: interface::parse_check_cfg(options.check_cfgs.clone()),
+ crate_cfg: interface::parse_cfgspecs(&early_error_handler, cfgs),
+ crate_check_cfg: interface::parse_check_cfg(
+ &early_error_handler,
+ options.check_cfgs.clone(),
+ ),
input,
output_file: None,
output_dir: None,
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index 8aaad8bce..dac762e9f 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -121,6 +121,11 @@ pub(crate) struct Cache {
pub(crate) intra_doc_links: FxHashMap<ItemId, FxIndexSet<clean::ItemLink>>,
/// Cfg that have been hidden via #![doc(cfg_hide(...))]
pub(crate) hidden_cfg: FxHashSet<clean::cfg::Cfg>,
+
+ /// Contains the list of `DefId`s which have been inlined. It is used when generating files
+ /// to check if a stripped item should get its file generated or not: if it's inside a
+ /// `#[doc(hidden)]` item or a private one and not inlined, it shouldn't get a file.
+ pub(crate) inlined_items: DefIdSet,
}
/// This struct is used to wrap the `cache` and `tcx` in order to run `DocFolder`.
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index d963d6092..54c0cd2ef 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -347,13 +347,19 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
}
} else {
let mut br_with_padding = String::with_capacity(6 * indent + 28);
- br_with_padding.push_str("\n");
+ br_with_padding.push('\n');
- let padding_amount =
- if ending == Ending::Newline { indent + 4 } else { indent + "fn where ".len() };
+ let where_indent = 3;
+ let padding_amount = if ending == Ending::Newline {
+ indent + 4
+ } else if indent == 0 {
+ 4
+ } else {
+ indent + where_indent + "where ".len()
+ };
for _ in 0..padding_amount {
- br_with_padding.push_str(" ");
+ br_with_padding.push(' ');
}
let where_preds = where_preds.to_string().replace('\n', &br_with_padding);
@@ -370,7 +376,8 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
let where_preds = where_preds.replacen(&br_with_padding, " ", 1);
let mut clause = br_with_padding;
- clause.truncate(clause.len() - "where ".len());
+ // +1 is for `\n`.
+ clause.truncate(indent + 1 + where_indent);
write!(clause, "<span class=\"where\">where{where_preds}</span>")?;
clause
@@ -1257,9 +1264,9 @@ impl clean::Impl {
};
primitive_link_fragment(f, PrimitiveType::Tuple, &format!("fn ({name}₁, {name}₂, …, {name}ₙ{ellipsis})"), "#trait-implementations-1", cx)?;
// Write output.
- if let clean::FnRetTy::Return(ty) = &bare_fn.decl.output {
+ if !bare_fn.decl.output.is_unit() {
write!(f, " -> ")?;
- fmt_type(ty, f, use_absolute, cx)?;
+ fmt_type(&bare_fn.decl.output, f, use_absolute, cx)?;
}
} else if let Some(ty) = self.kind.as_blanket_ty() {
fmt_type(ty, f, use_absolute, cx)?;
@@ -1296,22 +1303,6 @@ impl clean::Arguments {
}
}
-impl clean::FnRetTy {
- pub(crate) fn print<'a, 'tcx: 'a>(
- &'a self,
- cx: &'a Context<'tcx>,
- ) -> impl fmt::Display + 'a + Captures<'tcx> {
- display_fn(move |f| match self {
- clean::Return(clean::Tuple(tys)) if tys.is_empty() => Ok(()),
- clean::Return(ty) if f.alternate() => {
- write!(f, " -> {:#}", ty.print(cx))
- }
- clean::Return(ty) => write!(f, " -&gt; {}", ty.print(cx)),
- clean::DefaultReturn => Ok(()),
- })
- }
-}
-
impl clean::BareFunctionDecl {
fn print_hrtb_with_space<'a, 'tcx: 'a>(
&'a self,
@@ -1366,7 +1357,7 @@ impl clean::FnDecl {
"({args:#}{ellipsis}){arrow:#}",
args = self.inputs.print(cx),
ellipsis = ellipsis,
- arrow = self.output.print(cx)
+ arrow = self.print_output(cx)
)
} else {
write!(
@@ -1374,7 +1365,7 @@ impl clean::FnDecl {
"({args}{ellipsis}){arrow}",
args = self.inputs.print(cx),
ellipsis = ellipsis,
- arrow = self.output.print(cx)
+ arrow = self.print_output(cx)
)
}
})
@@ -1417,7 +1408,7 @@ impl clean::FnDecl {
let amp = if f.alternate() { "&" } else { "&amp;" };
write!(f, "(")?;
- if let Some(n) = line_wrapping_indent {
+ if let Some(n) = line_wrapping_indent && !self.inputs.values.is_empty() {
write!(f, "\n{}", Indent(n + 4))?;
}
for (i, input) in self.inputs.values.iter().enumerate() {
@@ -1464,9 +1455,22 @@ impl clean::FnDecl {
Some(n) => write!(f, "\n{})", Indent(n))?,
};
- fmt::Display::fmt(&self.output.print(cx), f)?;
+ fmt::Display::fmt(&self.print_output(cx), f)?;
Ok(())
}
+
+ fn print_output<'a, 'tcx: 'a>(
+ &'a self,
+ cx: &'a Context<'tcx>,
+ ) -> impl fmt::Display + 'a + Captures<'tcx> {
+ display_fn(move |f| match &self.output {
+ clean::Tuple(tys) if tys.is_empty() => Ok(()),
+ ty if f.alternate() => {
+ write!(f, " -> {:#}", ty.print(cx))
+ }
+ ty => write!(f, " -&gt; {}", ty.print(cx)),
+ })
+ }
}
pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>(
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 6ab849c92..8c5871d91 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -55,6 +55,7 @@ struct PageLayout<'a> {
sidebar: String,
content: String,
krate_with_trailing_slash: String,
+ rust_channel: &'static str,
pub(crate) rustdoc_version: &'a str,
}
@@ -82,6 +83,7 @@ pub(crate) fn render<T: Print, S: Print>(
sidebar,
content,
krate_with_trailing_slash,
+ rust_channel: *crate::clean::utils::DOC_CHANNEL,
rustdoc_version,
}
.render()
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 9bb20022c..fd00277e2 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -381,7 +381,6 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
Some(Event::Code(text)) => {
trace!("saw code {}", text);
if let Some(link) = self.shortcut_link {
- trace!("original text was {}", link.original_text);
// NOTE: this only replaces if the code block is the *entire* text.
// If only part of the link has code highlighting, the disambiguator will not be removed.
// e.g. [fn@`f`]
@@ -390,8 +389,11 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
// So we could never be sure we weren't replacing too much:
// [fn@my_`f`unc] is treated the same as [my_func()] in that pass.
//
- // NOTE: &[1..len() - 1] is to strip the backticks
- if **text == link.original_text[1..link.original_text.len() - 1] {
+ // NOTE: .get(1..len() - 1) is to strip the backticks
+ if let Some(link) = self.links.iter().find(|l| {
+ l.href == link.href
+ && Some(&**text) == l.original_text.get(1..l.original_text.len() - 1)
+ }) {
debug!("replacing {} with {}", text, link.new_text);
*text = CowStr::Borrowed(&link.new_text);
}
@@ -402,9 +404,12 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
Some(Event::Text(text)) => {
trace!("saw text {}", text);
if let Some(link) = self.shortcut_link {
- trace!("original text was {}", link.original_text);
// NOTE: same limitations as `Event::Code`
- if **text == *link.original_text {
+ if let Some(link) = self
+ .links
+ .iter()
+ .find(|l| l.href == link.href && **text == *l.original_text)
+ {
debug!("replacing {} with {}", text, link.new_text);
*text = CowStr::Borrowed(&link.new_text);
}
@@ -781,7 +786,7 @@ impl<'tcx> ExtraInfo<'tcx> {
ExtraInfo { def_id, sp, tcx }
}
- fn error_invalid_codeblock_attr(&self, msg: String, help: &str) {
+ fn error_invalid_codeblock_attr(&self, msg: String, help: &'static str) {
if let Some(def_id) = self.def_id.as_local() {
self.tcx.struct_span_lint_hir(
crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
@@ -1520,7 +1525,6 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
map.insert("toggle-all-docs".into(), 1);
map.insert("all-types".into(), 1);
map.insert("default-settings".into(), 1);
- map.insert("rustdoc-vars".into(), 1);
map.insert("sidebar-vars".into(), 1);
map.insert("copy-path".into(), 1);
map.insert("TOC".into(), 1);
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 56af257fd..4c4762636 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -73,6 +73,8 @@ pub(crate) struct Context<'tcx> {
pub(crate) include_sources: bool,
/// Collection of all types with notable traits referenced in the current module.
pub(crate) types_with_notable_traits: FxHashSet<clean::Type>,
+ /// Field used during rendering, to know if we're inside an inlined item.
+ pub(crate) is_inside_inlined_module: bool,
}
// `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
@@ -171,6 +173,19 @@ impl<'tcx> Context<'tcx> {
}
fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String {
+ let mut render_redirect_pages = self.render_redirect_pages;
+ // If the item is stripped but inlined, links won't point to the item so no need to generate
+ // a file for it.
+ if it.is_stripped() &&
+ let Some(def_id) = it.def_id() &&
+ def_id.is_local()
+ {
+ if self.is_inside_inlined_module || self.shared.cache.inlined_items.contains(&def_id) {
+ // For now we're forced to generate a redirect page for stripped items until
+ // `record_extern_fqn` correctly points to external items.
+ render_redirect_pages = true;
+ }
+ }
let mut title = String::new();
if !is_module {
title.push_str(it.name.unwrap().as_str());
@@ -205,7 +220,7 @@ impl<'tcx> Context<'tcx> {
tyname.as_str()
};
- if !self.render_redirect_pages {
+ if !render_redirect_pages {
let clone_shared = Rc::clone(&self.shared);
let page = layout::Page {
css_class: tyname_s,
@@ -545,6 +560,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
shared: Rc::new(scx),
include_sources,
types_with_notable_traits: FxHashSet::default(),
+ is_inside_inlined_module: false,
};
if emit_crate {
@@ -574,6 +590,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
shared: Rc::clone(&self.shared),
include_sources: self.include_sources,
types_with_notable_traits: FxHashSet::default(),
+ is_inside_inlined_module: self.is_inside_inlined_module,
}
}
@@ -768,12 +785,22 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
info!("Recursing into {}", self.dst.display());
- let buf = self.render_item(item, true);
- // buf will be empty if the module is stripped and there is no redirect for it
- if !buf.is_empty() {
- self.shared.ensure_dir(&self.dst)?;
- let joint_dst = self.dst.join("index.html");
- self.shared.fs.write(joint_dst, buf)?;
+ if !item.is_stripped() {
+ let buf = self.render_item(item, true);
+ // buf will be empty if the module is stripped and there is no redirect for it
+ if !buf.is_empty() {
+ self.shared.ensure_dir(&self.dst)?;
+ let joint_dst = self.dst.join("index.html");
+ self.shared.fs.write(joint_dst, buf)?;
+ }
+ }
+ if !self.is_inside_inlined_module {
+ if let Some(def_id) = item.def_id() && self.cache().inlined_items.contains(&def_id) {
+ self.is_inside_inlined_module = true;
+ }
+ } else if item.is_doc_hidden() {
+ // We're not inside an inlined module anymore since this one cannot be re-exported.
+ self.is_inside_inlined_module = false;
}
// Render sidebar-items.js used throughout this module.
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 9e3b5d10a..f923f9054 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -421,11 +421,10 @@ fn document<'a, 'cx: 'a>(
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))?;
+ write!(f, "{}", document_full_collapsible(item, cx, heading_offset))
} else {
- write!(f, "{}", document_full(item, cx, heading_offset))?;
+ write!(f, "{}", document_full(item, cx, heading_offset))
}
- Ok(())
})
}
@@ -787,10 +786,12 @@ fn assoc_type(
indent: usize,
cx: &Context<'_>,
) {
+ let tcx = cx.tcx();
write!(
w,
- "{indent}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
+ "{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
indent = " ".repeat(indent),
+ vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
href = assoc_href_attr(it, link, cx),
name = it.name.as_ref().unwrap(),
generics = generics.print(cx),
@@ -798,10 +799,11 @@ fn assoc_type(
if !bounds.is_empty() {
write!(w, ": {}", print_generic_bounds(bounds, cx))
}
- write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline));
+ // Render the default before the where-clause which aligns with the new recommended style. See #89122.
if let Some(default) = default {
write!(w, " = {}", default.print(cx))
}
+ write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline));
}
fn assoc_method(
@@ -844,7 +846,7 @@ fn assoc_method(
+ name.as_str().len()
+ generics_len;
- let notable_traits = d.output.as_return().and_then(|output| notable_traits_button(output, cx));
+ let notable_traits = notable_traits_button(&d.output, cx);
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
header_len += 4;
@@ -858,8 +860,8 @@ fn assoc_method(
w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len());
write!(
w,
- "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a{href} class=\"fn\">{name}</a>\
- {generics}{decl}{notable_traits}{where_clause}",
+ "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn \
+ <a{href} class=\"fn\">{name}</a>{generics}{decl}{notable_traits}{where_clause}",
indent = indent_str,
vis = vis,
constness = constness,
@@ -1038,9 +1040,9 @@ fn render_attributes_in_pre<'a, 'b: 'a>(
// When an attribute is rendered inside a <code> tag, it is formatted using
// a div to produce a newline after it.
-fn render_attributes_in_code(w: &mut Buffer, it: &clean::Item, tcx: TyCtxt<'_>) {
- for a in it.attributes(tcx, false) {
- write!(w, "<div class=\"code-attribute\">{}</div>", a);
+fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, tcx: TyCtxt<'_>) {
+ for attr in it.attributes(tcx, false) {
+ write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
}
}
@@ -1282,6 +1284,11 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option<String> {
let mut has_notable_trait = false;
+ if ty.is_unit() {
+ // Very common fast path.
+ return None;
+ }
+
let did = ty.def_id(cx.cache())?;
// Box has pass-through impls for Read, Write, Iterator, and Future when the
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 62027a3fa..383e3c170 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -9,7 +9,6 @@ use rustc_middle::middle::stability;
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::{kw, sym, Symbol};
-use std::borrow::Borrow;
use std::cell::{RefCell, RefMut};
use std::cmp::Ordering;
use std::fmt;
@@ -40,6 +39,110 @@ use crate::html::{highlight, static_files};
use askama::Template;
use itertools::Itertools;
+/// Generates an Askama template struct for rendering items with common methods.
+///
+/// Usage:
+/// ```ignore (illustrative)
+/// item_template!(
+/// #[template(path = "<template.html>", /* additional values */)]
+/// /* additional meta items */
+/// struct MyItem<'a, 'cx> {
+/// cx: RefCell<&'a mut Context<'cx>>,
+/// it: &'a clean::Item,
+/// /* additional fields */
+/// },
+/// methods = [ /* method names (comma separated; refer to macro definition of `item_template_methods!()`) */ ]
+/// )
+/// ```
+///
+/// NOTE: ensure that the generic lifetimes (`'a`, `'cx`) and
+/// required fields (`cx`, `it`) are identical (in terms of order and definition).
+macro_rules! item_template {
+ (
+ $(#[$meta:meta])*
+ struct $name:ident<'a, 'cx> {
+ cx: RefCell<&'a mut Context<'cx>>,
+ it: &'a clean::Item,
+ $($field_name:ident: $field_ty:ty),*,
+ },
+ methods = [$($methods:tt),* $(,)?]
+ ) => {
+ #[derive(Template)]
+ $(#[$meta])*
+ struct $name<'a, 'cx> {
+ cx: RefCell<&'a mut Context<'cx>>,
+ it: &'a clean::Item,
+ $($field_name: $field_ty),*
+ }
+
+ impl<'a, 'cx: 'a> ItemTemplate<'a, 'cx> for $name<'a, 'cx> {
+ fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>) {
+ (&self.it, self.cx.borrow_mut())
+ }
+ }
+
+ impl<'a, 'cx: 'a> $name<'a, 'cx> {
+ item_template_methods!($($methods)*);
+ }
+ };
+}
+
+/// Implement common methods for item template structs generated by `item_template!()`.
+///
+/// NOTE: this macro is intended to be used only by `item_template!()`.
+macro_rules! item_template_methods {
+ () => {};
+ (document $($rest:tt)*) => {
+ fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let (item, mut cx) = self.item_and_mut_cx();
+ let v = document(*cx, item, None, HeadingOffset::H2);
+ write!(f, "{v}")
+ })
+ }
+ item_template_methods!($($rest)*);
+ };
+ (document_type_layout $($rest:tt)*) => {
+ fn document_type_layout<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let (item, cx) = self.item_and_mut_cx();
+ let def_id = item.item_id.expect_def_id();
+ let v = document_type_layout(*cx, def_id);
+ write!(f, "{v}")
+ })
+ }
+ item_template_methods!($($rest)*);
+ };
+ (render_attributes_in_pre $($rest:tt)*) => {
+ fn render_attributes_in_pre<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let (item, cx) = self.item_and_mut_cx();
+ let tcx = cx.tcx();
+ let v = render_attributes_in_pre(item, "", tcx);
+ write!(f, "{v}")
+ })
+ }
+ item_template_methods!($($rest)*);
+ };
+ (render_assoc_items $($rest:tt)*) => {
+ fn render_assoc_items<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let (item, mut cx) = self.item_and_mut_cx();
+ let def_id = item.item_id.expect_def_id();
+ let v = render_assoc_items(*cx, item, def_id, AssocItemRender::All);
+ write!(f, "{v}")
+ })
+ }
+ item_template_methods!($($rest)*);
+ };
+ ($method:ident $($rest:tt)*) => {
+ compile_error!(concat!("unknown method: ", stringify!($method)));
+ };
+ ($token:tt $($rest:tt)*) => {
+ compile_error!(concat!("unexpected token: ", stringify!($token)));
+ };
+}
+
const ITEM_TABLE_OPEN: &str = "<ul class=\"item-table\">";
const ITEM_TABLE_CLOSE: &str = "</ul>";
const ITEM_TABLE_ROW_OPEN: &str = "<li>";
@@ -222,49 +325,6 @@ trait ItemTemplate<'a, 'cx: 'a>: askama::Template + fmt::Display {
fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>);
}
-fn item_template_document<'a: 'b, 'b, 'cx: 'a>(
- templ: &'b impl ItemTemplate<'a, 'cx>,
-) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
- display_fn(move |f| {
- let (item, mut cx) = templ.item_and_mut_cx();
- let v = document(*cx, item, None, HeadingOffset::H2);
- write!(f, "{v}")
- })
-}
-
-fn item_template_document_type_layout<'a: 'b, 'b, 'cx: 'a>(
- templ: &'b impl ItemTemplate<'a, 'cx>,
-) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
- display_fn(move |f| {
- let (item, cx) = templ.item_and_mut_cx();
- let def_id = item.item_id.expect_def_id();
- let v = document_type_layout(*cx, def_id);
- write!(f, "{v}")
- })
-}
-
-fn item_template_render_attributes_in_pre<'a: 'b, 'b, 'cx: 'a>(
- templ: &'b impl ItemTemplate<'a, 'cx>,
-) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
- display_fn(move |f| {
- let (item, cx) = templ.item_and_mut_cx();
- let tcx = cx.tcx();
- let v = render_attributes_in_pre(item, "", tcx);
- write!(f, "{v}")
- })
-}
-
-fn item_template_render_assoc_items<'a: 'b, 'b, 'cx: 'a>(
- templ: &'b impl ItemTemplate<'a, 'cx>,
-) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
- display_fn(move |f| {
- let (item, mut cx) = templ.item_and_mut_cx();
- let def_id = item.item_id.expect_def_id();
- let v = render_assoc_items(*cx, item, def_id, AssocItemRender::All);
- write!(f, "{v}")
- })
-}
-
fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) {
write!(w, "{}", document(cx, item, None, HeadingOffset::H2));
@@ -587,8 +647,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
+ name.as_str().len()
+ generics_len;
- let notable_traits =
- f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx));
+ let notable_traits = notable_traits_button(&f.decl.output, cx);
wrap_item(w, |w| {
w.reserve(header_len);
@@ -1102,7 +1161,12 @@ 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) {
+fn item_trait_alias(
+ w: &mut impl fmt::Write,
+ cx: &mut Context<'_>,
+ it: &clean::Item,
+ t: &clean::TraitAlias,
+) {
wrap_item(w, |w| {
write!(
w,
@@ -1112,19 +1176,25 @@ fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &
print_where_clause(&t.generics, cx, 0, Ending::Newline),
bounds(&t.bounds, true, cx),
attrs = render_attributes_in_pre(it, "", cx.tcx()),
- );
+ )
+ .unwrap();
});
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
-
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
// 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.
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
+ .unwrap();
}
-fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) {
+fn item_opaque_ty(
+ w: &mut impl fmt::Write,
+ cx: &mut Context<'_>,
+ it: &clean::Item,
+ t: &clean::OpaqueTy,
+) {
wrap_item(w, |w| {
write!(
w,
@@ -1134,16 +1204,18 @@ fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &cl
where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
bounds = bounds(&t.bounds, false, cx),
attrs = render_attributes_in_pre(it, "", cx.tcx()),
- );
+ )
+ .unwrap();
});
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
// 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.
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
+ .unwrap();
}
fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Typedef) {
@@ -1176,19 +1248,15 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea
}
fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) {
- #[derive(Template)]
- #[template(path = "item_union.html")]
- struct ItemUnion<'a, 'cx> {
- cx: RefCell<&'a mut Context<'cx>>,
- it: &'a clean::Item,
- s: &'a clean::Union,
- }
-
- impl<'a, 'cx: 'a> ItemTemplate<'a, 'cx> for ItemUnion<'a, 'cx> {
- fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>) {
- (self.it, self.cx.borrow_mut())
- }
- }
+ item_template!(
+ #[template(path = "item_union.html")]
+ struct ItemUnion<'a, 'cx> {
+ cx: RefCell<&'a mut Context<'cx>>,
+ it: &'a clean::Item,
+ s: &'a clean::Union,
+ },
+ methods = [document, document_type_layout, render_attributes_in_pre, render_assoc_items]
+ );
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
@@ -1198,6 +1266,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
write!(f, "{v}")
})
}
+
fn document_field<'b>(
&'b self,
field: &'a clean::Item,
@@ -1208,10 +1277,12 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
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,
@@ -1420,37 +1491,41 @@ fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
-fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &clean::ProcMacro) {
- wrap_item(w, |w| {
+fn item_proc_macro(
+ w: &mut impl fmt::Write,
+ cx: &mut Context<'_>,
+ it: &clean::Item,
+ m: &clean::ProcMacro,
+) {
+ wrap_item(w, |buffer| {
let name = it.name.expect("proc-macros always have names");
match m.kind {
MacroKind::Bang => {
- write!(w, "{}!() {{ /* proc-macro */ }}", name);
+ write!(buffer, "{name}!() {{ /* proc-macro */ }}").unwrap();
}
MacroKind::Attr => {
- write!(w, "#[{}]", name);
+ write!(buffer, "#[{name}]").unwrap();
}
MacroKind::Derive => {
- write!(w, "#[derive({})]", name);
+ write!(buffer, "#[derive({name})]").unwrap();
if !m.helpers.is_empty() {
- w.push_str("\n{\n");
- w.push_str(" // Attributes available to this derive:\n");
+ buffer.write_str("\n{\n // Attributes available to this derive:\n").unwrap();
for attr in &m.helpers {
- writeln!(w, " #[{}]", attr);
+ writeln!(buffer, " #[{attr}]").unwrap();
}
- w.push_str("}\n");
+ buffer.write_str("}\n").unwrap();
}
}
}
});
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
}
-fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
+fn item_primitive(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) {
let def_id = it.item_id.expect_def_id();
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
- write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
+ write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)).unwrap();
} else {
// We handle the "reference" primitive type on its own because we only want to list
// implementations on generic types.
@@ -1560,8 +1635,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
}
fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) {
- let mut buffer = Buffer::new();
- wrap_item(&mut buffer, |buffer| {
+ wrap_item(w, |buffer| {
render_attributes_in_code(buffer, it, cx.tcx());
write!(
buffer,
@@ -1570,29 +1644,29 @@ fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item,
mutability = s.mutability.print_with_space(),
name = it.name.unwrap(),
typ = s.type_.print(cx)
- );
+ )
+ .unwrap();
});
- write!(w, "{}", buffer.into_inner()).unwrap();
-
write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
}
-fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
- wrap_item(w, |w| {
- w.write_str("extern {\n");
- render_attributes_in_code(w, it, cx.tcx());
+fn item_foreign_type(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) {
+ wrap_item(w, |buffer| {
+ buffer.write_str("extern {\n").unwrap();
+ render_attributes_in_code(buffer, it, cx.tcx());
write!(
- w,
+ buffer,
" {}type {};\n}}",
visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx),
it.name.unwrap(),
- );
+ )
+ .unwrap();
});
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
-
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
+ .unwrap();
}
fn item_keyword(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
@@ -1666,13 +1740,14 @@ fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>)
bounds
}
-fn wrap_item<F>(w: &mut Buffer, f: F)
+fn wrap_item<W, F>(w: &mut W, f: F)
where
- F: FnOnce(&mut Buffer),
+ W: fmt::Write,
+ F: FnOnce(&mut W),
{
- w.write_str(r#"<pre class="rust item-decl"><code>"#);
+ write!(w, r#"<pre class="rust item-decl"><code>"#).unwrap();
f(w);
- w.write_str("</code></pre>");
+ write!(w, "</code></pre>").unwrap();
}
#[derive(PartialEq, Eq)]
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index 846299f02..f34be120d 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -7,7 +7,7 @@ use rustc_span::symbol::Symbol;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use crate::clean;
-use crate::clean::types::{FnRetTy, Function, Generics, ItemId, Type, WherePredicate};
+use crate::clean::types::{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;
@@ -656,22 +656,9 @@ fn get_fn_inputs_and_outputs<'tcx>(
}
let mut ret_types = Vec::new();
- match decl.output {
- FnRetTy::Return(ref return_type) => {
- add_generics_and_bounds_as_types(
- self_,
- generics,
- return_type,
- tcx,
- 0,
- &mut ret_types,
- cache,
- );
- if ret_types.is_empty() {
- ret_types.push(get_index_type(return_type, vec![]));
- }
- }
- _ => {}
- };
+ add_generics_and_bounds_as_types(self_, generics, &decl.output, tcx, 0, &mut ret_types, cache);
+ if ret_types.is_empty() {
+ ret_types.push(get_index_type(&decl.output, vec![]));
+ }
(all_types, ret_types)
}
diff --git a/src/librustdoc/html/render/type_layout.rs b/src/librustdoc/html/render/type_layout.rs
index c9b95b1e6..0bc32ea5a 100644
--- a/src/librustdoc/html/render/type_layout.rs
+++ b/src/librustdoc/html/render/type_layout.rs
@@ -17,7 +17,7 @@ use crate::html::render::Context;
#[template(path = "type_layout.html")]
struct TypeLayout<'cx> {
variants: Vec<(Symbol, TypeLayoutSize)>,
- type_layout_size: Result<TypeLayoutSize, LayoutError<'cx>>,
+ type_layout_size: Result<TypeLayoutSize, &'cx LayoutError<'cx>>,
}
#[derive(Template)]
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index a7d5f4977..b7f455259 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -8,6 +8,7 @@
:root {
--nav-sub-mobile-padding: 8px;
+ --search-typename-width: 6.75rem;
}
/* See FiraSans-LICENSE.txt for the Fira Sans license. */
@@ -213,7 +214,7 @@ a.anchor,
h1 a,
.search-results a,
.stab,
-.result-name .primitive > i, .result-name .keyword > i {
+.result-name i {
color: var(--main-color);
}
@@ -869,14 +870,11 @@ so that we can apply CSS-filters to change the arrow color in themes */
gap: 1em;
}
-.search-results > a > div {
- flex: 1;
-}
-
.search-results > a > div.desc {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
+ flex: 2;
}
.search-results a:hover,
@@ -884,12 +882,28 @@ so that we can apply CSS-filters to change the arrow color in themes */
background-color: var(--search-result-link-focus-background-color);
}
+.search-results .result-name {
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ flex: 3;
+}
.search-results .result-name span.alias {
color: var(--search-results-alias-color);
}
-.search-results .result-name span.grey {
+.search-results .result-name .grey {
color: var(--search-results-grey-color);
}
+.search-results .result-name .typename {
+ color: var(--search-results-grey-color);
+ font-size: 0.875rem;
+ width: var(--search-typename-width);
+}
+.search-results .result-name .path {
+ word-break: break-all;
+ max-width: calc(100% - var(--search-typename-width));
+ display: inline-block;
+}
.popover {
position: absolute;
@@ -957,6 +971,8 @@ so that we can apply CSS-filters to change the arrow color in themes */
display: flex;
padding: 3px;
margin-bottom: 5px;
+ align-items: center;
+ vertical-align: text-bottom;
}
.item-name .stab {
margin-left: 0.3125em;
@@ -968,11 +984,9 @@ so that we can apply CSS-filters to change the arrow color in themes */
color: var(--main-color);
background-color: var(--stab-background-color);
width: fit-content;
- align-items: center;
white-space: pre-wrap;
border-radius: 3px;
- display: inline-flex;
- vertical-align: text-bottom;
+ display: inline;
}
.stab.portability > code {
@@ -1179,6 +1193,10 @@ a.test-arrow:hover {
position: relative;
}
+.code-header a.tooltip:hover {
+ color: var(--link-color);
+}
+
/* placeholder thunk so that the mouse can easily travel from "(i)" to popover
the resulting "hover tunnel" is a stepped triangle, approximating
https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown */
@@ -1191,6 +1209,14 @@ a.tooltip:hover::after {
content: "\00a0";
}
+/* This animation is layered onto the mistake-proofing delay for dismissing
+ a hovered tooltip, to ensure it feels responsive even with the delay.
+ */
+.fade-out {
+ opacity: 0;
+ transition: opacity 0.45s cubic-bezier(0, 0, 0.1, 1.0);
+}
+
.popover.tooltip .content {
margin: 0.25em 0.5em;
}
@@ -1712,6 +1738,16 @@ in source-script.js
.search-results > a > div.desc, .item-table > li > div.desc {
padding-left: 2em;
}
+ .search-results .result-name {
+ display: block;
+ }
+ .search-results .result-name .typename {
+ width: initial;
+ margin-right: 0;
+ }
+ .search-results .result-name .typename, .search-results .result-name .path {
+ display: inline;
+ }
.source-sidebar-expanded .source .sidebar {
max-width: 100vw;
diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
index 8b931f74e..f697abd07 100644
--- a/src/librustdoc/html/static/js/externs.js
+++ b/src/librustdoc/html/static/js/externs.js
@@ -53,7 +53,7 @@ let ParsedQuery;
* parent: (Object|null|undefined),
* path: string,
* ty: (Number|null|number),
- * type: (Array<?>|null)
+ * type: FunctionSearchType?
* }}
*/
let Row;
@@ -135,7 +135,7 @@ let RawFunctionType;
/**
* @typedef {{
* inputs: Array<FunctionType>,
- * outputs: Array<FunctionType>,
+ * output: Array<FunctionType>,
* }}
*/
let FunctionSearchType;
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index bccf675c1..254b0d8bf 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -4,6 +4,13 @@
"use strict";
+// The amount of time that the cursor must remain still over a hover target before
+// revealing a tooltip.
+//
+// https://www.nngroup.com/articles/timing-exposing-content/
+window.RUSTDOC_TOOLTIP_HOVER_MS = 300;
+window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS = 450;
+
// 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) {
@@ -270,14 +277,18 @@ function preLoadCss(cssUrl) {
searchState.mouseMovedAfterSearch = false;
document.title = searchState.title;
},
- hideResults: () => {
- switchDisplayedElement(null);
+ removeQueryParameters: () => {
+ // We change the document title.
document.title = searchState.titleBeforeSearch;
- // We also remove the query parameter from the URL.
if (browserSupportsHistoryApi()) {
history.replaceState(null, "", getNakedUrl() + window.location.hash);
}
},
+ hideResults: () => {
+ switchDisplayedElement(null);
+ // We also remove the query parameter from the URL.
+ searchState.removeQueryParameters();
+ },
getQueryStringParams: () => {
const params = {};
window.location.search.substring(1).split("&").
@@ -772,6 +783,13 @@ function preLoadCss(cssUrl) {
});
});
+ /**
+ * Show a tooltip immediately.
+ *
+ * @param {DOMElement} e - The tooltip's anchor point. The DOM is consulted to figure
+ * out what the tooltip should contain, and where it should be
+ * positioned.
+ */
function showTooltip(e) {
const notable_ty = e.getAttribute("data-notable-ty");
if (!window.NOTABLE_TRAITS && notable_ty) {
@@ -782,8 +800,10 @@ function preLoadCss(cssUrl) {
throw new Error("showTooltip() called with notable without any notable traits!");
}
}
+ // Make this function idempotent. If the tooltip is already shown, avoid doing extra work
+ // and leave it alone.
if (window.CURRENT_TOOLTIP_ELEMENT && window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE === e) {
- // Make this function idempotent.
+ clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
return;
}
window.hideAllModals(false);
@@ -791,11 +811,18 @@ function preLoadCss(cssUrl) {
if (notable_ty) {
wrapper.innerHTML = "<div class=\"content\">" +
window.NOTABLE_TRAITS[notable_ty] + "</div>";
- } else if (e.getAttribute("title") !== undefined) {
- const titleContent = document.createElement("div");
- titleContent.className = "content";
- titleContent.appendChild(document.createTextNode(e.getAttribute("title")));
- wrapper.appendChild(titleContent);
+ } else {
+ // Replace any `title` attribute with `data-title` to avoid double tooltips.
+ if (e.getAttribute("title") !== null) {
+ e.setAttribute("data-title", e.getAttribute("title"));
+ e.removeAttribute("title");
+ }
+ if (e.getAttribute("data-title") !== null) {
+ const titleContent = document.createElement("div");
+ titleContent.className = "content";
+ titleContent.appendChild(document.createTextNode(e.getAttribute("data-title")));
+ wrapper.appendChild(titleContent);
+ }
}
wrapper.className = "tooltip popover";
const focusCatcher = document.createElement("div");
@@ -824,17 +851,77 @@ function preLoadCss(cssUrl) {
wrapper.style.visibility = "";
window.CURRENT_TOOLTIP_ELEMENT = wrapper;
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE = e;
+ clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
+ wrapper.onpointerenter = function(ev) {
+ // If this is a synthetic touch event, ignore it. A click event will be along shortly.
+ if (ev.pointerType !== "mouse") {
+ return;
+ }
+ clearTooltipHoverTimeout(e);
+ };
wrapper.onpointerleave = function(ev) {
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
if (ev.pointerType !== "mouse") {
return;
}
- if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) {
- hideTooltip(true);
+ if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(ev.relatedTarget, e)) {
+ // See "Tooltip pointer leave gesture" below.
+ setTooltipHoverTimeout(e, false);
+ addClass(wrapper, "fade-out");
}
};
}
+ /**
+ * Show or hide the tooltip after a timeout. If a timeout was already set before this function
+ * was called, that timeout gets cleared. If the tooltip is already in the requested state,
+ * this function will still clear any pending timeout, but otherwise do nothing.
+ *
+ * @param {DOMElement} element - The tooltip's anchor point. The DOM is consulted to figure
+ * out what the tooltip should contain, and where it should be
+ * positioned.
+ * @param {boolean} show - If true, the tooltip will be made visible. If false, it will
+ * be hidden.
+ */
+ function setTooltipHoverTimeout(element, show) {
+ clearTooltipHoverTimeout(element);
+ if (!show && !window.CURRENT_TOOLTIP_ELEMENT) {
+ // To "hide" an already hidden element, just cancel its timeout.
+ return;
+ }
+ if (show && window.CURRENT_TOOLTIP_ELEMENT) {
+ // To "show" an already visible element, just cancel its timeout.
+ return;
+ }
+ if (window.CURRENT_TOOLTIP_ELEMENT &&
+ window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE !== element) {
+ // Don't do anything if another tooltip is already visible.
+ return;
+ }
+ element.TOOLTIP_HOVER_TIMEOUT = setTimeout(() => {
+ if (show) {
+ showTooltip(element);
+ } else if (!element.TOOLTIP_FORCE_VISIBLE) {
+ hideTooltip(false);
+ }
+ }, show ? window.RUSTDOC_TOOLTIP_HOVER_MS : window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS);
+ }
+
+ /**
+ * If a show/hide timeout was set by `setTooltipHoverTimeout`, cancel it. If none exists,
+ * do nothing.
+ *
+ * @param {DOMElement} element - The tooltip's anchor point,
+ * as passed to `setTooltipHoverTimeout`.
+ */
+ function clearTooltipHoverTimeout(element) {
+ if (element.TOOLTIP_HOVER_TIMEOUT !== undefined) {
+ removeClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");
+ clearTimeout(element.TOOLTIP_HOVER_TIMEOUT);
+ delete element.TOOLTIP_HOVER_TIMEOUT;
+ }
+ }
+
function tooltipBlurHandler(event) {
if (window.CURRENT_TOOLTIP_ELEMENT &&
!elemIsInParent(document.activeElement, window.CURRENT_TOOLTIP_ELEMENT) &&
@@ -854,6 +941,12 @@ function preLoadCss(cssUrl) {
}
}
+ /**
+ * Hide the current tooltip immediately.
+ *
+ * @param {boolean} focus - If set to `true`, move keyboard focus to the tooltip anchor point.
+ * If set to `false`, leave keyboard focus alone.
+ */
function hideTooltip(focus) {
if (window.CURRENT_TOOLTIP_ELEMENT) {
if (window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE) {
@@ -864,6 +957,7 @@ function preLoadCss(cssUrl) {
}
const body = document.getElementsByTagName("body")[0];
body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);
+ clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
window.CURRENT_TOOLTIP_ELEMENT = null;
}
}
@@ -886,7 +980,14 @@ function preLoadCss(cssUrl) {
if (ev.pointerType !== "mouse") {
return;
}
- showTooltip(this);
+ setTooltipHoverTimeout(this, true);
+ };
+ e.onpointermove = function(ev) {
+ // If this is a synthetic touch event, ignore it. A click event will be along shortly.
+ if (ev.pointerType !== "mouse") {
+ return;
+ }
+ setTooltipHoverTimeout(this, true);
};
e.onpointerleave = function(ev) {
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
@@ -895,7 +996,38 @@ function preLoadCss(cssUrl) {
}
if (!this.TOOLTIP_FORCE_VISIBLE &&
!elemIsInParent(ev.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT)) {
- hideTooltip(true);
+ // Tooltip pointer leave gesture:
+ //
+ // Designing a good hover microinteraction is a matter of guessing user
+ // intent from what are, literally, vague gestures. In this case, guessing if
+ // hovering in or out of the tooltip base is intentional or not.
+ //
+ // To figure this out, a few different techniques are used:
+ //
+ // * When the mouse pointer enters a tooltip anchor point, its hitbox is grown
+ // on the bottom, where the popover is/will appear. Search "hover tunnel" in
+ // rustdoc.css for the implementation.
+ // * There's a delay when the mouse pointer enters the popover base anchor, in
+ // case the mouse pointer was just passing through and the user didn't want
+ // to open it.
+ // * Similarly, a delay is added when exiting the anchor, or the popover
+ // itself, before hiding it.
+ // * A fade-out animation is layered onto the pointer exit delay to immediately
+ // inform the user that they successfully dismissed the popover, while still
+ // providing a way for them to cancel it if it was a mistake and they still
+ // wanted to interact with it.
+ // * No animation is used for revealing it, because we don't want people to try
+ // to interact with an element while it's in the middle of fading in: either
+ // they're allowed to interact with it while it's fading in, meaning it can't
+ // serve as mistake-proofing for the popover, or they can't, but
+ // they might try and be frustrated.
+ //
+ // See also:
+ // * https://www.nngroup.com/articles/timing-exposing-content/
+ // * https://www.nngroup.com/articles/tooltip-guidelines/
+ // * https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown
+ setTooltipHoverTimeout(e, false);
+ addClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");
}
};
});
@@ -918,9 +1050,10 @@ function preLoadCss(cssUrl) {
function buildHelpMenu() {
const book_info = document.createElement("span");
+ const channel = getVar("channel");
book_info.className = "top";
- book_info.innerHTML = "You can find more information in \
- <a href=\"https://doc.rust-lang.org/rustdoc/\">the rustdoc book</a>.";
+ book_info.innerHTML = `You can find more information in \
+<a href="https://doc.rust-lang.org/${channel}/rustdoc/">the rustdoc book</a>.`;
const shortcuts = [
["?", "Show this help dialog"],
@@ -940,6 +1073,9 @@ function preLoadCss(cssUrl) {
div_shortcuts.innerHTML = "<h2>Keyboard Shortcuts</h2><dl>" + shortcuts + "</dl></div>";
const infos = [
+ `For a full list of all search features, take a look <a \
+href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
+#the-search-interface">here</a>.`,
"Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \
restrict the search to a given item kind.",
"Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \
@@ -949,6 +1085,10 @@ function preLoadCss(cssUrl) {
<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 functions that accept or return \
+ <a href=\"https://doc.rust-lang.org/std/primitive.slice.html\">slices</a> and \
+ <a href=\"https://doc.rust-lang.org/std/primitive.array.html\">arrays</a> by writing \
+ square brackets (e.g., <code>-&gt; [u8]</code> or <code>[] -&gt; Option</code>)",
"Look for items inside another one by searching for a path: <code>vec::Vec</code>",
].map(x => "<p>" + x + "</p>").join("");
const div_infos = document.createElement("div");
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 62afe40bb..51d8e81ca 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -35,6 +35,35 @@ const itemTypes = [
"traitalias",
];
+const longItemTypes = [
+ "module",
+ "extern crate",
+ "re-export",
+ "struct",
+ "enum",
+ "function",
+ "type alias",
+ "static",
+ "trait",
+ "",
+ "trait method",
+ "method",
+ "struct field",
+ "enum variant",
+ "macro",
+ "primitive type",
+ "assoc type",
+ "constant",
+ "assoc const",
+ "union",
+ "foreign type",
+ "keyword",
+ "existential type",
+ "attribute macro",
+ "derive macro",
+ "trait alias",
+];
+
// used for special search precedence
const TY_PRIMITIVE = itemTypes.indexOf("primitive");
const TY_KEYWORD = itemTypes.indexOf("keyword");
@@ -208,6 +237,46 @@ function initSearch(rawSearchIndex) {
let typeNameIdMap;
const ALIASES = new Map();
+ /**
+ * Special type name IDs for searching by array.
+ */
+ let typeNameIdOfArray;
+ /**
+ * Special type name IDs for searching by slice.
+ */
+ let typeNameIdOfSlice;
+ /**
+ * Special type name IDs for searching by both array and slice (`[]` syntax).
+ */
+ let typeNameIdOfArrayOrSlice;
+
+ /**
+ * Add an item to the type Name->ID map, or, if one already exists, use it.
+ * Returns the number. If name is "" or null, return -1 (pure generic).
+ *
+ * This is effectively string interning, so that function matching can be
+ * done more quickly. Two types with the same name but different item kinds
+ * get the same ID.
+ *
+ * @param {string} name
+ *
+ * @returns {integer}
+ */
+ function buildTypeMapIndex(name) {
+
+ if (name === "" || name === null) {
+ return -1;
+ }
+
+ if (typeNameIdMap.has(name)) {
+ return typeNameIdMap.get(name);
+ } else {
+ const id = typeNameIdMap.size;
+ typeNameIdMap.set(name, id);
+ return id;
+ }
+ }
+
function isWhitespace(c) {
return " \t\n\r".indexOf(c) !== -1;
}
@@ -217,11 +286,11 @@ function initSearch(rawSearchIndex) {
}
function isEndCharacter(c) {
- return ",>-".indexOf(c) !== -1;
+ return ",>-]".indexOf(c) !== -1;
}
function isStopCharacter(c) {
- return isWhitespace(c) || isEndCharacter(c);
+ return isEndCharacter(c);
}
function isErrorCharacter(c) {
@@ -317,18 +386,69 @@ function initSearch(rawSearchIndex) {
* @return {boolean}
*/
function isSeparatorCharacter(c) {
- return c === "," || isWhitespaceCharacter(c);
+ return c === ",";
}
- /**
- * Returns `true` if the given `c` character is a whitespace.
+/**
+ * Returns `true` if the given `c` character is a path separator. For example
+ * `:` in `a::b` or a whitespace in `a b`.
*
* @param {string} c
*
* @return {boolean}
*/
- function isWhitespaceCharacter(c) {
- return c === " " || c === "\t";
+ function isPathSeparator(c) {
+ return c === ":" || isWhitespace(c);
+ }
+
+ /**
+ * Returns `true` if the previous character is `lookingFor`.
+ *
+ * @param {ParserState} parserState
+ * @param {String} lookingFor
+ *
+ * @return {boolean}
+ */
+ function prevIs(parserState, lookingFor) {
+ let pos = parserState.pos;
+ while (pos > 0) {
+ const c = parserState.userQuery[pos - 1];
+ if (c === lookingFor) {
+ return true;
+ } else if (!isWhitespace(c)) {
+ break;
+ }
+ pos -= 1;
+ }
+ return false;
+ }
+
+ /**
+ * Returns `true` if the last element in the `elems` argument has generics.
+ *
+ * @param {Array<QueryElement>} elems
+ * @param {ParserState} parserState
+ *
+ * @return {boolean}
+ */
+ function isLastElemGeneric(elems, parserState) {
+ return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) ||
+ prevIs(parserState, ">");
+ }
+
+ /**
+ * Increase current parser position until it doesn't find a whitespace anymore.
+ *
+ * @param {ParserState} parserState
+ */
+ function skipWhitespace(parserState) {
+ while (parserState.pos < parserState.userQuery.length) {
+ const c = parserState.userQuery[parserState.pos];
+ if (!isWhitespace(c)) {
+ break;
+ }
+ parserState.pos += 1;
+ }
}
/**
@@ -340,39 +460,78 @@ function initSearch(rawSearchIndex) {
* @return {QueryElement} - The newly created `QueryElement`.
*/
function createQueryElement(query, parserState, name, generics, isInGenerics) {
- if (name === "*" || (name.length === 0 && generics.length === 0)) {
- return;
+ const path = name.trim();
+ if (path.length === 0 && generics.length === 0) {
+ throw ["Unexpected ", parserState.userQuery[parserState.pos]];
+ } else if (path === "*") {
+ throw ["Unexpected ", "*"];
}
if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {
- throw ["You cannot have more than one element if you use quotes"];
- }
- const pathSegments = name.split("::");
- if (pathSegments.length > 1) {
- for (let i = 0, len = pathSegments.length; i < len; ++i) {
- const pathSegment = pathSegments[i];
-
- if (pathSegment.length === 0) {
- if (i === 0) {
- throw ["Paths cannot start with ", "::"];
- } else if (i + 1 === len) {
- throw ["Paths cannot end with ", "::"];
- }
- throw ["Unexpected ", "::::"];
- }
+ throw ["Cannot have more than one element if you use quotes"];
+ }
+ const typeFilter = parserState.typeFilter;
+ parserState.typeFilter = null;
+ if (name === "!") {
+ if (typeFilter !== null && typeFilter !== "primitive") {
+ throw [
+ "Invalid search type: primitive never type ",
+ "!",
+ " and ",
+ typeFilter,
+ " both specified",
+ ];
}
+ if (generics.length !== 0) {
+ throw [
+ "Never type ",
+ "!",
+ " does not accept generic parameters",
+ ];
+ }
+ return {
+ name: "never",
+ id: -1,
+ fullPath: ["never"],
+ pathWithoutLast: [],
+ pathLast: "never",
+ generics: [],
+ typeFilter: "primitive",
+ };
}
+ if (path.startsWith("::")) {
+ throw ["Paths cannot start with ", "::"];
+ } else if (path.endsWith("::")) {
+ throw ["Paths cannot end with ", "::"];
+ } else if (path.includes("::::")) {
+ throw ["Unexpected ", "::::"];
+ } else if (path.includes(" ::")) {
+ throw ["Unexpected ", " ::"];
+ } else if (path.includes(":: ")) {
+ throw ["Unexpected ", ":: "];
+ }
+ const pathSegments = path.split(/::|\s+/);
// In case we only have something like `<p>`, there is no name.
if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) {
- throw ["Found generics without a path"];
+ if (generics.length > 0 || prevIs(parserState, ">")) {
+ throw ["Found generics without a path"];
+ } else {
+ throw ["Unexpected ", parserState.userQuery[parserState.pos]];
+ }
+ }
+ for (const [i, pathSegment] of pathSegments.entries()) {
+ if (pathSegment === "!") {
+ if (i !== 0) {
+ throw ["Never type ", "!", " is not associated item"];
+ }
+ pathSegments[i] = "never";
+ }
}
parserState.totalElems += 1;
if (isInGenerics) {
parserState.genericsElems += 1;
}
- const typeFilter = parserState.typeFilter;
- parserState.typeFilter = null;
return {
- name: name,
+ name: name.trim(),
id: -1,
fullPath: pathSegments,
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
@@ -408,28 +567,40 @@ function initSearch(rawSearchIndex) {
foundExclamation = parserState.pos;
} else if (isErrorCharacter(c)) {
throw ["Unexpected ", c];
- } else if (
- isStopCharacter(c) ||
- isSpecialStartCharacter(c) ||
- isSeparatorCharacter(c)
- ) {
- break;
- } else if (c === ":") { // If we allow paths ("str::string" for example).
- if (!isPathStart(parserState)) {
- break;
+ } else if (isPathSeparator(c)) {
+ if (c === ":") {
+ if (!isPathStart(parserState)) {
+ break;
+ }
+ // Skip current ":".
+ parserState.pos += 1;
+ } else {
+ while (parserState.pos + 1 < parserState.length) {
+ const next_c = parserState.userQuery[parserState.pos + 1];
+ if (!isWhitespace(next_c)) {
+ break;
+ }
+ parserState.pos += 1;
+ }
}
if (foundExclamation !== -1) {
- if (start <= (end - 2)) {
+ if (foundExclamation !== start &&
+ isIdentCharacter(parserState.userQuery[foundExclamation - 1])
+ ) {
throw ["Cannot have associated items in macros"];
} else {
- // if start == end - 1, we got the never type
// while the never type has no associated macros, we still
// can parse a path like that
foundExclamation = -1;
}
}
- // Skip current ":".
- parserState.pos += 1;
+ } else if (
+ c === "[" ||
+ isStopCharacter(c) ||
+ isSpecialStartCharacter(c) ||
+ isSeparatorCharacter(c)
+ ) {
+ break;
} else {
throw ["Unexpected ", c];
}
@@ -438,7 +609,10 @@ function initSearch(rawSearchIndex) {
end = parserState.pos;
}
// if start == end - 1, we got the never type
- if (foundExclamation !== -1 && start <= (end - 2)) {
+ if (foundExclamation !== -1 &&
+ foundExclamation !== start &&
+ isIdentCharacter(parserState.userQuery[foundExclamation - 1])
+ ) {
if (parserState.typeFilter === null) {
parserState.typeFilter = "macro";
} else if (parserState.typeFilter !== "macro") {
@@ -464,37 +638,71 @@ function initSearch(rawSearchIndex) {
function getNextElem(query, parserState, elems, isInGenerics) {
const generics = [];
+ skipWhitespace(parserState);
let start = parserState.pos;
let end;
- // We handle the strings on their own mostly to make code easier to follow.
- if (parserState.userQuery[parserState.pos] === "\"") {
- start += 1;
- getStringElem(query, parserState, isInGenerics);
- end = parserState.pos - 1;
+ if (parserState.userQuery[parserState.pos] === "[") {
+ parserState.pos += 1;
+ getItemsBefore(query, parserState, generics, "]");
+ const typeFilter = parserState.typeFilter;
+ if (typeFilter !== null && typeFilter !== "primitive") {
+ throw [
+ "Invalid search type: primitive ",
+ "[]",
+ " and ",
+ typeFilter,
+ " both specified",
+ ];
+ }
+ parserState.typeFilter = null;
+ parserState.totalElems += 1;
+ if (isInGenerics) {
+ parserState.genericsElems += 1;
+ }
+ elems.push({
+ name: "[]",
+ id: -1,
+ fullPath: ["[]"],
+ pathWithoutLast: [],
+ pathLast: "[]",
+ generics,
+ typeFilter: "primitive",
+ });
} else {
- end = getIdentEndPosition(parserState);
- }
- if (parserState.pos < parserState.length &&
- parserState.userQuery[parserState.pos] === "<"
- ) {
- if (start >= end) {
- throw ["Found generics without a path"];
+ const isStringElem = parserState.userQuery[start] === "\"";
+ // We handle the strings on their own mostly to make code easier to follow.
+ if (isStringElem) {
+ start += 1;
+ getStringElem(query, parserState, isInGenerics);
+ end = parserState.pos - 1;
+ } else {
+ end = getIdentEndPosition(parserState);
}
- parserState.pos += 1;
- getItemsBefore(query, parserState, generics, ">");
- }
- if (start >= end && generics.length === 0) {
- return;
+ if (parserState.pos < parserState.length &&
+ parserState.userQuery[parserState.pos] === "<"
+ ) {
+ if (start >= end) {
+ throw ["Found generics without a path"];
+ }
+ parserState.pos += 1;
+ getItemsBefore(query, parserState, generics, ">");
+ }
+ if (isStringElem) {
+ skipWhitespace(parserState);
+ }
+ if (start >= end && generics.length === 0) {
+ return;
+ }
+ elems.push(
+ createQueryElement(
+ query,
+ parserState,
+ parserState.userQuery.slice(start, end),
+ generics,
+ isInGenerics
+ )
+ );
}
- elems.push(
- createQueryElement(
- query,
- parserState,
- parserState.userQuery.slice(start, end),
- generics,
- isInGenerics
- )
- );
}
/**
@@ -518,6 +726,17 @@ function initSearch(rawSearchIndex) {
const oldTypeFilter = parserState.typeFilter;
parserState.typeFilter = null;
+ let extra = "";
+ if (endChar === ">") {
+ extra = "<";
+ } else if (endChar === "]") {
+ extra = "[";
+ } else if (endChar === "") {
+ extra = "->";
+ } else {
+ extra = endChar;
+ }
+
while (parserState.pos < parserState.length) {
const c = parserState.userQuery[parserState.pos];
if (c === endChar) {
@@ -535,7 +754,7 @@ function initSearch(rawSearchIndex) {
if (elems.length === 0) {
throw ["Expected type filter before ", ":"];
} else if (query.literalSearch) {
- throw ["You cannot use quotes on type filter"];
+ throw ["Cannot use quotes on type filter"];
}
// The type filter doesn't count as an element since it's a modifier.
const typeFilterElem = elems.pop();
@@ -547,43 +766,39 @@ function initSearch(rawSearchIndex) {
foundStopChar = true;
continue;
} else if (isEndCharacter(c)) {
- let extra = "";
- if (endChar === ">") {
- extra = "<";
- } else if (endChar === "") {
- extra = "->";
- } else {
- extra = endChar;
- }
throw ["Unexpected ", c, " after ", extra];
}
if (!foundStopChar) {
+ let extra = [];
+ if (isLastElemGeneric(query.elems, parserState)) {
+ extra = [" after ", ">"];
+ } else if (prevIs(parserState, "\"")) {
+ throw ["Cannot have more than one element if you use quotes"];
+ }
if (endChar !== "") {
throw [
"Expected ",
- ",", // comma
- ", ",
- "&nbsp;", // whitespace
+ ",",
" or ",
endChar,
+ ...extra,
", found ",
c,
];
}
throw [
"Expected ",
- ",", // comma
- " or ",
- "&nbsp;", // whitespace
+ ",",
+ ...extra,
", found ",
c,
];
}
const posBefore = parserState.pos;
start = parserState.pos;
- getNextElem(query, parserState, elems, endChar === ">");
+ getNextElem(query, parserState, elems, endChar !== "");
if (endChar !== "" && parserState.pos >= parserState.length) {
- throw ["Unclosed ", "<"];
+ throw ["Unclosed ", extra];
}
// 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
@@ -594,7 +809,7 @@ function initSearch(rawSearchIndex) {
foundStopChar = false;
}
if (parserState.pos >= parserState.length && endChar !== "") {
- throw ["Unclosed ", "<"];
+ throw ["Unclosed ", extra];
}
// We are either at the end of the string or on the `endChar` character, let's move forward
// in any case.
@@ -610,11 +825,17 @@ function initSearch(rawSearchIndex) {
* @param {ParserState} parserState
*/
function checkExtraTypeFilterCharacters(start, parserState) {
- const query = parserState.userQuery;
+ const query = parserState.userQuery.slice(start, parserState.pos).trim();
- for (let pos = start; pos < parserState.pos; ++pos) {
- if (!isIdentCharacter(query[pos]) && !isWhitespaceCharacter(query[pos])) {
- throw ["Unexpected ", query[pos], " in type filter"];
+ for (const c in query) {
+ if (!isIdentCharacter(query[c])) {
+ throw [
+ "Unexpected ",
+ query[c],
+ " in type filter (before ",
+ ":",
+ ")",
+ ];
}
}
}
@@ -646,12 +867,17 @@ function initSearch(rawSearchIndex) {
throw ["Unexpected ", c];
} else if (c === ":" && !isPathStart(parserState)) {
if (parserState.typeFilter !== null) {
- throw ["Unexpected ", ":"];
- }
- if (query.elems.length === 0) {
+ throw [
+ "Unexpected ",
+ ":",
+ " (expected path after type filter ",
+ parserState.typeFilter + ":",
+ ")",
+ ];
+ } else if (query.elems.length === 0) {
throw ["Expected type filter before ", ":"];
} else if (query.literalSearch) {
- throw ["You cannot use quotes on type filter"];
+ throw ["Cannot use quotes on type filter"];
}
// The type filter doesn't count as an element since it's a modifier.
const typeFilterElem = query.elems.pop();
@@ -662,29 +888,36 @@ function initSearch(rawSearchIndex) {
query.literalSearch = false;
foundStopChar = true;
continue;
+ } else if (isWhitespace(c)) {
+ skipWhitespace(parserState);
+ continue;
}
if (!foundStopChar) {
+ let extra = "";
+ if (isLastElemGeneric(query.elems, parserState)) {
+ extra = [" after ", ">"];
+ } else if (prevIs(parserState, "\"")) {
+ throw ["Cannot have more than one element if you use quotes"];
+ }
if (parserState.typeFilter !== null) {
throw [
"Expected ",
- ",", // comma
- ", ",
- "&nbsp;", // whitespace
+ ",",
" or ",
- "->", // arrow
+ "->",
+ ...extra,
", found ",
c,
];
}
throw [
"Expected ",
- ",", // comma
+ ",",
", ",
- "&nbsp;", // whitespace
- ", ",
- ":", // colon
+ ":",
" or ",
- "->", // arrow
+ "->",
+ ...extra,
", found ",
c,
];
@@ -699,11 +932,18 @@ function initSearch(rawSearchIndex) {
foundStopChar = false;
}
if (parserState.typeFilter !== null) {
- throw ["Unexpected ", ":", " (expected path after type filter)"];
+ throw [
+ "Unexpected ",
+ ":",
+ " (expected path after type filter ",
+ parserState.typeFilter + ":",
+ ")",
+ ];
}
while (parserState.pos < parserState.length) {
if (isReturnArrow(parserState)) {
parserState.pos += 2;
+ skipWhitespace(parserState);
// Get returned elements.
getItemsBefore(query, parserState, query.returned, "");
// Nothing can come afterward!
@@ -778,9 +1018,10 @@ function initSearch(rawSearchIndex) {
* The supported syntax by this parser is as follow:
*
* ident = *(ALPHA / DIGIT / "_")
- * path = ident *(DOUBLE-COLON ident) [!]
- * arg = [type-filter *WS COLON *WS] path [generics]
- * type-sep = COMMA/WS *(COMMA/WS)
+ * path = ident *(DOUBLE-COLON/{WS} ident) [!]
+ * slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
+ * arg = [type-filter *WS COLON *WS] (path [generics] / slice)
+ * type-sep = *WS COMMA *(COMMA)
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
* generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
* CLOSE-ANGLE-BRACKET
@@ -821,6 +1062,8 @@ function initSearch(rawSearchIndex) {
*
* OPEN-ANGLE-BRACKET = "<"
* CLOSE-ANGLE-BRACKET = ">"
+ * OPEN-SQUARE-BRACKET = "["
+ * CLOSE-SQUARE-BRACKET = "]"
* COLON = ":"
* DOUBLE-COLON = "::"
* QUOTE = %x22
@@ -1103,98 +1346,182 @@ function initSearch(rawSearchIndex) {
}
/**
- * This function checks if the object (`row`) generics match the given type (`elem`)
- * generics. If there are no generics on `row`, `defaultDistance` is returned.
+ * This function checks generics in search query `queryElem` can all be found in the
+ * search index (`fnType`),
*
- * @param {Row} row - The object to check.
- * @param {QueryElement} elem - The element from the parsed query.
+ * @param {FunctionType} fnType - The object to check.
+ * @param {QueryElement} queryElem - The element from the parsed query.
*
- * @return {boolean} - Returns true if a match, false otherwise.
+ * @return {boolean} - Returns true if a match, false otherwise.
*/
- function checkGenerics(row, elem) {
- if (row.generics.length === 0 || elem.generics.length === 0) {
- return false;
- }
- // This function is called if the names match, but we need to make
- // sure that all generics match as well.
- //
+ function checkGenerics(fnType, queryElem) {
+ return unifyFunctionTypes(fnType.generics, queryElem.generics);
+ }
+ /**
+ * This function checks if a list of search query `queryElems` can all be found in the
+ * search index (`fnTypes`).
+ *
+ * @param {Array<FunctionType>} fnTypes - The objects to check.
+ * @param {Array<QueryElement>} queryElems - The elements from the parsed query.
+ *
+ * @return {boolean} - Returns true if a match, false otherwise.
+ */
+ function unifyFunctionTypes(fnTypes, queryElems) {
// This search engine implements order-agnostic unification. There
// should be no missing duplicates (generics have "bag semantics"),
// and the row is allowed to have extras.
- if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) {
- const elems = new Map();
- const addEntryToElems = function addEntryToElems(entry) {
- if (entry.id === -1) {
- // Pure generic, needs to check into it.
- for (const inner_entry of entry.generics) {
- addEntryToElems(inner_entry);
- }
- return;
+ if (queryElems.length === 0) {
+ return true;
+ }
+ if (!fnTypes || fnTypes.length === 0) {
+ return false;
+ }
+ /**
+ * @type Map<integer, QueryElement[]>
+ */
+ const queryElemSet = new Map();
+ const addQueryElemToQueryElemSet = function addQueryElemToQueryElemSet(queryElem) {
+ let currentQueryElemList;
+ if (queryElemSet.has(queryElem.id)) {
+ currentQueryElemList = queryElemSet.get(queryElem.id);
+ } else {
+ currentQueryElemList = [];
+ queryElemSet.set(queryElem.id, currentQueryElemList);
+ }
+ currentQueryElemList.push(queryElem);
+ };
+ for (const queryElem of queryElems) {
+ addQueryElemToQueryElemSet(queryElem);
+ }
+ /**
+ * @type Map<integer, FunctionType[]>
+ */
+ const fnTypeSet = new Map();
+ const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) {
+ // Pure generic, or an item that's not matched by any query elems.
+ // Try [unboxing] it.
+ //
+ // [unboxing]:
+ // http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
+ const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice);
+ if (fnType.id === -1 || !(
+ queryElemSet.has(fnType.id) ||
+ (fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) ||
+ (fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem)
+ )) {
+ for (const innerFnType of fnType.generics) {
+ addFnTypeToFnTypeSet(innerFnType);
}
- let currentEntryElems;
- if (elems.has(entry.id)) {
- currentEntryElems = elems.get(entry.id);
- } else {
- currentEntryElems = [];
- elems.set(entry.id, currentEntryElems);
+ return;
+ }
+ let currentQueryElemList = queryElemSet.get(fnType.id) || [];
+ let matchIdx = currentQueryElemList.findIndex(queryElem => {
+ return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
+ checkGenerics(fnType, queryElem);
+ });
+ if (matchIdx === -1 &&
+ (fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) &&
+ queryContainsArrayOrSliceElem
+ ) {
+ currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || [];
+ matchIdx = currentQueryElemList.findIndex(queryElem => {
+ return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
+ checkGenerics(fnType, queryElem);
+ });
+ }
+ // None of the query elems match the function type.
+ // Try [unboxing] it.
+ if (matchIdx === -1) {
+ for (const innerFnType of fnType.generics) {
+ addFnTypeToFnTypeSet(innerFnType);
}
- currentEntryElems.push(entry);
- };
- for (const entry of row.generics) {
- addEntryToElems(entry);
- }
- // We need to find the type that matches the most to remove it in order
- // to move forward.
- const handleGeneric = generic => {
- if (!elems.has(generic.id)) {
- return false;
+ return;
+ }
+ let currentFnTypeList;
+ if (fnTypeSet.has(fnType.id)) {
+ currentFnTypeList = fnTypeSet.get(fnType.id);
+ } else {
+ currentFnTypeList = [];
+ fnTypeSet.set(fnType.id, currentFnTypeList);
+ }
+ currentFnTypeList.push(fnType);
+ };
+ for (const fnType of fnTypes) {
+ addFnTypeToFnTypeSet(fnType);
+ }
+ const doHandleQueryElemList = (currentFnTypeList, queryElemList) => {
+ if (queryElemList.length === 0) {
+ return true;
+ }
+ // Multiple items in one list might match multiple items in another.
+ // Since an item with fewer generics can match an item with more, we
+ // need to check all combinations for a potential match.
+ const queryElem = queryElemList.pop();
+ const l = currentFnTypeList.length;
+ for (let i = 0; i < l; i += 1) {
+ const fnType = currentFnTypeList[i];
+ if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
+ continue;
}
- const matchElems = elems.get(generic.id);
- const matchIdx = matchElems.findIndex(tmp_elem => {
- if (generic.generics.length > 0 && !checkGenerics(tmp_elem, generic)) {
- return false;
+ if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) {
+ currentFnTypeList.splice(i, 1);
+ const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
+ if (result) {
+ return true;
}
- return typePassesFilter(generic.typeFilter, tmp_elem.ty);
- });
- if (matchIdx === -1) {
- return false;
+ currentFnTypeList.splice(i, 0, fnType);
}
- matchElems.splice(matchIdx, 1);
- if (matchElems.length === 0) {
- elems.delete(generic.id);
+ }
+ return false;
+ };
+ const handleQueryElemList = (id, queryElemList) => {
+ if (!fnTypeSet.has(id)) {
+ if (id === typeNameIdOfArrayOrSlice) {
+ return handleQueryElemList(typeNameIdOfSlice, queryElemList) ||
+ handleQueryElemList(typeNameIdOfArray, queryElemList);
}
- 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 false;
+ return false;
+ }
+ const currentFnTypeList = fnTypeSet.get(id);
+ if (currentFnTypeList.length < queryElemList.length) {
+ // It's not possible for all the query elems to find a match.
+ return false;
+ }
+ const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
+ if (result) {
+ // Found a solution.
+ // Any items that weren't used for it can be unboxed, and might form
+ // part of the solution for another item.
+ for (const innerFnType of currentFnTypeList) {
+ addFnTypeToFnTypeSet(innerFnType);
}
+ fnTypeSet.delete(id);
}
- for (const generic of elem.generics) {
- if (generic.typeFilter === -1 && !handleGeneric(generic)) {
- return false;
+ return result;
+ };
+ let queryElemSetSize = -1;
+ while (queryElemSetSize !== queryElemSet.size) {
+ queryElemSetSize = queryElemSet.size;
+ for (const [id, queryElemList] of queryElemSet) {
+ if (handleQueryElemList(id, queryElemList)) {
+ queryElemSet.delete(id);
}
}
- return true;
}
- return false;
+ return queryElemSetSize === 0;
}
/**
* This function checks if the object (`row`) matches the given type (`elem`) and its
* generics (if any).
*
- * @param {Row} row
+ * @param {Array<FunctionType>} list
* @param {QueryElement} elem - The element from the parsed query.
*
* @return {boolean} - Returns true if found, false otherwise.
*/
- function checkIfInGenerics(row, elem) {
- for (const entry of row.generics) {
+ function checkIfInList(list, elem) {
+ for (const entry of list) {
if (checkType(entry, elem)) {
return true;
}
@@ -1214,10 +1541,15 @@ function initSearch(rawSearchIndex) {
function checkType(row, elem) {
if (row.id === -1) {
// This is a pure "generic" search, no need to run other checks.
- return row.generics.length > 0 ? checkIfInGenerics(row, elem) : false;
+ return row.generics.length > 0 ? checkIfInList(row.generics, elem) : false;
}
- if (row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty)) {
+ const matchesExact = row.id === elem.id;
+ const matchesArrayOrSlice = elem.id === typeNameIdOfArrayOrSlice &&
+ (row.id === typeNameIdOfSlice || row.id === typeNameIdOfArray);
+
+ if ((matchesExact || matchesArrayOrSlice) &&
+ typePassesFilter(elem.typeFilter, row.ty)) {
if (elem.generics.length > 0) {
return checkGenerics(row, elem);
}
@@ -1227,59 +1559,7 @@ function initSearch(rawSearchIndex) {
// If the current item does not match, try [unboxing] the generic.
// [unboxing]:
// https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
- return checkIfInGenerics(row, elem);
- }
-
- /**
- * This function checks if the object (`row`) has an argument with the given type (`elem`).
- *
- * @param {Row} row
- * @param {QueryElement} elem - The element from the parsed query.
- * @param {Array<integer>} skipPositions - Do not return one of these positions.
- *
- * @return {integer} - Returns the position of the match, or -1 if none.
- */
- function findArg(row, elem, skipPositions) {
- if (row && row.type && row.type.inputs && row.type.inputs.length > 0) {
- let i = 0;
- for (const input of row.type.inputs) {
- if (skipPositions.indexOf(i) !== -1) {
- i += 1;
- continue;
- }
- if (checkType(input, elem)) {
- return i;
- }
- i += 1;
- }
- }
- return -1;
- }
-
- /**
- * This function checks if the object (`row`) returns the given type (`elem`).
- *
- * @param {Row} row
- * @param {QueryElement} elem - The element from the parsed query.
- * @param {Array<integer>} skipPositions - Do not return one of these positions.
- *
- * @return {integer} - Returns the position of the matching item, or -1 if none.
- */
- function checkReturned(row, elem, skipPositions) {
- if (row && row.type && row.type.output.length > 0) {
- let i = 0;
- for (const ret_ty of row.type.output) {
- if (skipPositions.indexOf(i) !== -1) {
- i += 1;
- continue;
- }
- if (checkType(ret_ty, elem)) {
- return i;
- }
- i += 1;
- }
- }
- return -1;
+ return checkIfInList(row.generics, elem);
}
function checkPath(contains, ty, maxEditDistance) {
@@ -1480,14 +1760,14 @@ function initSearch(rawSearchIndex) {
const fullId = row.id;
const searchWord = searchWords[pos];
- const in_args = findArg(row, elem, []);
- if (in_args !== -1) {
+ const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem);
+ if (in_args) {
// path_dist is 0 because no parent path information is currently stored
// in the search index
addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance);
}
- const returned = checkReturned(row, elem, []);
- if (returned !== -1) {
+ const returned = row.type && row.type.output && checkIfInList(row.type.output, elem);
+ if (returned) {
addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance);
}
@@ -1543,32 +1823,15 @@ function initSearch(rawSearchIndex) {
* @param {Object} results
*/
function handleArgs(row, pos, results) {
- if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
+ if (!row || (filterCrates !== null && row.crate !== filterCrates) || !row.type) {
return;
}
// 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 position = callback(
- row,
- elem,
- skipPositions
- );
- if (position !== -1) {
- skipPositions.push(position);
- } else {
- return false;
- }
- }
- return true;
- }
- if (!checkArgs(parsedQuery.elems, findArg)) {
+ if (!unifyFunctionTypes(row.type.inputs, parsedQuery.elems)) {
return;
}
- if (!checkArgs(parsedQuery.returned, checkReturned)) {
+ if (!unifyFunctionTypes(row.type.output, parsedQuery.returned)) {
return;
}
@@ -1655,12 +1918,9 @@ function initSearch(rawSearchIndex) {
elem = parsedQuery.returned[0];
for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
row = searchIndex[i];
- in_returned = checkReturned(
- row,
- elem,
- []
- );
- if (in_returned !== -1) {
+ in_returned = row.type &&
+ unifyFunctionTypes(row.type.output, parsedQuery.returned);
+ if (in_returned) {
addIntoResults(
results_others,
row.id,
@@ -1836,16 +2096,11 @@ function initSearch(rawSearchIndex) {
array.forEach(item => {
const name = item.name;
const type = itemTypes[item.ty];
+ const longType = longItemTypes[item.ty];
+ const typeName = longType.length !== 0 ? `${longType}` : "?";
length += 1;
- let extra = "";
- if (type === "primitive") {
- extra = " <i>(primitive type)</i>";
- } else if (type === "keyword") {
- extra = " <i>(keyword)</i>";
- }
-
const link = document.createElement("a");
link.className = "result-" + type;
link.href = item.href;
@@ -1863,13 +2118,18 @@ function initSearch(rawSearchIndex) {
alias.insertAdjacentHTML(
"beforeend",
- "<span class=\"grey\"><i>&nbsp;- see&nbsp;</i></span>");
+ "<i class=\"grey\">&nbsp;- see&nbsp;</i>");
resultName.appendChild(alias);
}
+
resultName.insertAdjacentHTML(
"beforeend",
- item.displayPath + "<span class=\"" + type + "\">" + name + extra + "</span>");
+ `\
+<span class="typename">${typeName}</span>\
+<div class="path">\
+ ${item.displayPath}<span class="${type}">${name}</span>\
+</div>`);
link.appendChild(resultName);
const description = document.createElement("div");
@@ -1916,6 +2176,20 @@ function initSearch(rawSearchIndex) {
if (go_to_first || (results.others.length === 1
&& getSettingValue("go-to-only-result") === "true")
) {
+ // Needed to force re-execution of JS when coming back to a page. Let's take this
+ // scenario as example:
+ //
+ // 1. You have the "Directly go to item in search if there is only one result" option
+ // enabled.
+ // 2. You make a search which results only one result, leading you automatically to
+ // this result.
+ // 3. You go back to previous page.
+ //
+ // Now, without the call below, the JS will not be re-executed and the previous state
+ // will be used, starting search again since the search input is not empty, leading you
+ // back to the previous page again.
+ window.onunload = () => {};
+ searchState.removeQueryParameters();
const elem = document.createElement("a");
elem.href = results.others[0].href;
removeClass(elem, "active");
@@ -1967,7 +2241,7 @@ function initSearch(rawSearchIndex) {
error.forEach((value, index) => {
value = value.split("<").join("&lt;").split(">").join("&gt;");
if (index % 2 !== 0) {
- error[index] = `<code>${value}</code>`;
+ error[index] = `<code>${value.replaceAll(" ", "&nbsp;")}</code>`;
} else {
error[index] = value;
}
@@ -2030,6 +2304,18 @@ function initSearch(rawSearchIndex) {
printTab(currentTab);
}
+ function updateSearchHistory(url) {
+ if (!browserSupportsHistoryApi()) {
+ return;
+ }
+ const params = searchState.getQueryStringParams();
+ if (!history.state && !params.search) {
+ history.pushState(null, "", url);
+ } else {
+ history.replaceState(null, "", url);
+ }
+ }
+
/**
* Perform a search based on the current state of the search input element
* and display the results.
@@ -2040,7 +2326,6 @@ function initSearch(rawSearchIndex) {
if (e) {
e.preventDefault();
}
-
const query = parseQuery(searchState.input.value.trim());
let filterCrates = getFilterCrates();
@@ -2066,15 +2351,7 @@ function initSearch(rawSearchIndex) {
// Because searching is incremental by character, only the most
// recent search query is added to the browser history.
- if (browserSupportsHistoryApi()) {
- const newURL = buildUrl(query.original, filterCrates);
-
- if (!history.state && !params.search) {
- history.pushState(null, "", newURL);
- } else {
- history.replaceState(null, "", newURL);
- }
- }
+ updateSearchHistory(buildUrl(query.original, filterCrates));
showResults(
execQuery(query, searchWords, filterCrates, window.currentCrate),
@@ -2083,34 +2360,6 @@ function initSearch(rawSearchIndex) {
}
/**
- * Add an item to the type Name->ID map, or, if one already exists, use it.
- * Returns the number. If name is "" or null, return -1 (pure generic).
- *
- * This is effectively string interning, so that function matching can be
- * done more quickly. Two types with the same name but different item kinds
- * get the same ID.
- *
- * @param {Map<string, integer>} typeNameIdMap
- * @param {string} name
- *
- * @returns {integer}
- */
- function buildTypeMapIndex(typeNameIdMap, name) {
-
- if (name === "" || name === null) {
- return -1;
- }
-
- if (typeNameIdMap.has(name)) {
- return typeNameIdMap.get(name);
- } else {
- const id = typeNameIdMap.size;
- typeNameIdMap.set(name, id);
- return id;
- }
- }
-
- /**
* Convert a list of RawFunctionType / ID to object-based FunctionType.
*
* Crates often have lots of functions in them, and it's common to have a large number of
@@ -2128,7 +2377,7 @@ function initSearch(rawSearchIndex) {
*
* @return {Array<FunctionSearchType>}
*/
- function buildItemSearchTypeAll(types, lowercasePaths, typeNameIdMap) {
+ function buildItemSearchTypeAll(types, lowercasePaths) {
const PATH_INDEX_DATA = 0;
const GENERICS_DATA = 1;
return types.map(type => {
@@ -2140,15 +2389,14 @@ function initSearch(rawSearchIndex) {
pathIndex = type[PATH_INDEX_DATA];
generics = buildItemSearchTypeAll(
type[GENERICS_DATA],
- lowercasePaths,
- typeNameIdMap
+ lowercasePaths
);
}
return {
// `0` is used as a sentinel because it's fewer bytes than `null`
id: pathIndex === 0
? -1
- : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
+ : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
generics: generics,
};
@@ -2171,7 +2419,7 @@ function initSearch(rawSearchIndex) {
*
* @return {null|FunctionSearchType}
*/
- function buildFunctionSearchType(functionSearchType, lowercasePaths, typeNameIdMap) {
+ function buildFunctionSearchType(functionSearchType, lowercasePaths) {
const INPUTS_DATA = 0;
const OUTPUT_DATA = 1;
// `0` is used as a sentinel because it's fewer bytes than `null`
@@ -2184,15 +2432,14 @@ function initSearch(rawSearchIndex) {
inputs = [{
id: pathIndex === 0
? -1
- : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
+ : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
generics: [],
}];
} else {
inputs = buildItemSearchTypeAll(
functionSearchType[INPUTS_DATA],
- lowercasePaths,
- typeNameIdMap
+ lowercasePaths
);
}
if (functionSearchType.length > 1) {
@@ -2201,15 +2448,14 @@ function initSearch(rawSearchIndex) {
output = [{
id: pathIndex === 0
? -1
- : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
+ : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
generics: [],
}];
} else {
output = buildItemSearchTypeAll(
functionSearchType[OUTPUT_DATA],
- lowercasePaths,
- typeNameIdMap
+ lowercasePaths
);
}
} else {
@@ -2233,6 +2479,12 @@ function initSearch(rawSearchIndex) {
let currentIndex = 0;
let id = 0;
+ // Initialize type map indexes for primitive list types
+ // that can be searched using `[]` syntax.
+ typeNameIdOfArray = buildTypeMapIndex("array");
+ typeNameIdOfSlice = buildTypeMapIndex("slice");
+ typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
+
for (const crate in rawSearchIndex) {
if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
continue;
@@ -2363,8 +2615,7 @@ function initSearch(rawSearchIndex) {
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
type: buildFunctionSearchType(
itemFunctionSearchTypes[i],
- lowercasePaths,
- typeNameIdMap
+ lowercasePaths
),
id: id,
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
@@ -2566,13 +2817,8 @@ function initSearch(rawSearchIndex) {
function updateCrate(ev) {
if (ev.target.value === "all crates") {
// If we don't remove it from the URL, it'll be picked up again by the search.
- const params = searchState.getQueryStringParams();
const query = searchState.input.value.trim();
- if (!history.state && !params.search) {
- history.pushState(null, "", buildUrl(query, null));
- } else {
- history.replaceState(null, "", buildUrl(query, null));
- }
+ updateSearchHistory(buildUrl(query, null));
}
// In case you "cut" the entry from the search input, then change the crate filter
// before paste back the previous search, you get the old search results without
diff --git a/src/librustdoc/html/static/js/source-script.js b/src/librustdoc/html/static/js/source-script.js
index d999f3b36..6eb991360 100644
--- a/src/librustdoc/html/static/js/source-script.js
+++ b/src/librustdoc/html/static/js/source-script.js
@@ -3,13 +3,13 @@
// Local js definitions:
/* global addClass, getCurrentValue, onEachLazy, removeClass, browserSupportsHistoryApi */
-/* global updateLocalStorage */
+/* global updateLocalStorage, getVar */
"use strict";
(function() {
-const rootPath = document.getElementById("rustdoc-vars").attributes["data-root-path"].value;
+const rootPath = getVar("root-path");
const NAME_OFFSET = 0;
const DIRS_OFFSET = 1;
diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js
index 93979a944..71961f6f2 100644
--- a/src/librustdoc/html/static/js/storage.js
+++ b/src/librustdoc/html/static/js/storage.js
@@ -108,7 +108,7 @@ function getCurrentValue(name) {
// 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");
+ const el = document.querySelector("head > meta[name='rustdoc-vars']");
return el ? el.attributes["data-" + name].value : null;
});
diff --git a/src/librustdoc/html/templates/item_info.html b/src/librustdoc/html/templates/item_info.html
index d2ea9bdae..9e65ae95e 100644
--- a/src/librustdoc/html/templates/item_info.html
+++ b/src/librustdoc/html/templates/item_info.html
@@ -1,5 +1,5 @@
{% if !items.is_empty() %}
- <span class="item-info"> {# #}
+ <span class="item-info">
{% for item in items %}
{{item|safe}} {# #}
{% endfor %}
diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html
index c21967005..f6d2fa348 100644
--- a/src/librustdoc/html/templates/item_union.html
+++ b/src/librustdoc/html/templates/item_union.html
@@ -1,17 +1,18 @@
<pre class="rust item-decl"><code>
- {{ self::item_template_render_attributes_in_pre(self.borrow()) | safe }}
+ {{ self.render_attributes_in_pre() | safe }}
{{ self.render_union() | safe }}
</code></pre>
-{{ self::item_template_document(self.borrow()) | safe }}
+{{ 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 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 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>
@@ -19,5 +20,5 @@
{{ self.document_field(field) | safe }}
{% endfor %}
{% endif %}
-{{ self::item_template_render_assoc_items(self.borrow()) | safe }}
-{{ self::item_template_document_type_layout(self.borrow()) | safe }}
+{{ 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 9133f899a..d4ec9c34b 100644
--- a/src/librustdoc/html/templates/page.html
+++ b/src/librustdoc/html/templates/page.html
@@ -24,13 +24,14 @@
{% endfor %}
></script> {# #}
{% endif %}
- <div id="rustdoc-vars" {#+ #}
+ <meta name="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-channel="{{rust_channel}}" {#+ #}
data-search-js="{{files.search_js}}" {#+ #}
data-settings-js="{{files.settings_js}}" {#+ #}
data-settings-css="{{files.settings_css}}" {#+ #}
@@ -38,7 +39,6 @@
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> {# #}
diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html
index edabac9a0..68a295ae0 100644
--- a/src/librustdoc/html/templates/print_item.html
+++ b/src/librustdoc/html/templates/print_item.html
@@ -1,5 +1,5 @@
<div class="main-heading"> {# #}
- <h1> {# #}
+ <h1>
{{typ}}
{# The breadcrumbs of the item path, like std::string #}
{% for component in path_components %}
@@ -12,7 +12,7 @@
alt="Copy item path"> {# #}
</button> {# #}
</h1> {# #}
- <span class="out-of-band"> {# #}
+ <span class="out-of-band">
{% if !stability_since_raw.is_empty() %}
{{ stability_since_raw|safe +}} · {#+ #}
{% endif %}
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 935bb721f..91cd55b11 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -624,10 +624,7 @@ impl FromWithTcx<clean::FnDecl> for FnDecl {
.into_iter()
.map(|arg| (arg.name.to_string(), arg.type_.into_tcx(tcx)))
.collect(),
- output: match output {
- clean::FnRetTy::Return(t) => Some(t.into_tcx(tcx)),
- clean::FnRetTy::DefaultReturn => None,
- },
+ output: if output.is_unit() { None } else { Some(output.into_tcx(tcx)) },
c_variadic,
}
}
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 12c622e02..f28deae79 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -6,7 +6,6 @@
#![feature(array_methods)]
#![feature(assert_matches)]
#![feature(box_patterns)]
-#![feature(drain_filter)]
#![feature(impl_trait_in_assoc_type)]
#![feature(iter_intersperse)]
#![feature(lazy_cell)]
@@ -80,8 +79,7 @@ use rustc_errors::ErrorGuaranteed;
use rustc_interface::interface;
use rustc_middle::ty::TyCtxt;
use rustc_session::config::{make_crate_type_option, ErrorOutputType, RustcOptGroup};
-use rustc_session::getopts;
-use rustc_session::{early_error, early_warn};
+use rustc_session::{getopts, EarlyErrorHandler};
use crate::clean::utils::DOC_RUST_LANG_ORG_CHANNEL;
@@ -156,6 +154,8 @@ pub fn main() {
}
}
+ let mut handler = EarlyErrorHandler::new(ErrorOutputType::default());
+
rustc_driver::install_ice_hook(
"https://github.com/rust-lang/rust/issues/new\
?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md",
@@ -171,11 +171,12 @@ pub fn main() {
// NOTE: The reason this doesn't show double logging when `download-rustc = false` and
// `debug_logging = true` is because all rustc logging goes to its version of tracing (the one
// in the sysroot), and all of rustdoc's logging goes to its version (the one in Cargo.toml).
- init_logging();
- rustc_driver::init_env_logger("RUSTDOC_LOG");
- let exit_code = rustc_driver::catch_with_exit_code(|| match get_args() {
- Some(args) => main_args(&args),
+ init_logging(&handler);
+ rustc_driver::init_env_logger(&handler, "RUSTDOC_LOG");
+
+ let exit_code = rustc_driver::catch_with_exit_code(|| match get_args(&handler) {
+ Some(args) => main_args(&mut handler, &args),
_ =>
{
#[allow(deprecated)]
@@ -185,22 +186,19 @@ pub fn main() {
process::exit(exit_code);
}
-fn init_logging() {
+fn init_logging(handler: &EarlyErrorHandler) {
let color_logs = match std::env::var("RUSTDOC_LOG_COLOR").as_deref() {
Ok("always") => true,
Ok("never") => false,
Ok("auto") | Err(VarError::NotPresent) => io::stdout().is_terminal(),
- Ok(value) => early_error(
- ErrorOutputType::default(),
- format!("invalid log color value '{}': expected one of always, never, or auto", value),
- ),
- Err(VarError::NotUnicode(value)) => early_error(
- ErrorOutputType::default(),
- format!(
- "invalid log color value '{}': expected one of always, never, or auto",
- value.to_string_lossy()
- ),
- ),
+ Ok(value) => handler.early_error(format!(
+ "invalid log color value '{}': expected one of always, never, or auto",
+ value
+ )),
+ Err(VarError::NotUnicode(value)) => handler.early_error(format!(
+ "invalid log color value '{}': expected one of always, never, or auto",
+ value.to_string_lossy()
+ )),
};
let filter = tracing_subscriber::EnvFilter::from_env("RUSTDOC_LOG");
let layer = tracing_tree::HierarchicalLayer::default()
@@ -220,16 +218,13 @@ fn init_logging() {
tracing::subscriber::set_global_default(subscriber).unwrap();
}
-fn get_args() -> Option<Vec<String>> {
+fn get_args(handler: &EarlyErrorHandler) -> Option<Vec<String>> {
env::args_os()
.enumerate()
.map(|(i, arg)| {
arg.into_string()
.map_err(|arg| {
- early_warn(
- ErrorOutputType::default(),
- format!("Argument {} is not valid Unicode: {:?}", i, arg),
- );
+ handler.early_warn(format!("Argument {} is not valid Unicode: {:?}", i, arg));
})
.ok()
})
@@ -711,7 +706,7 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>(
}
}
-fn main_args(at_args: &[String]) -> MainResult {
+fn main_args(handler: &mut EarlyErrorHandler, at_args: &[String]) -> MainResult {
// Throw away the first argument, the name of the binary.
// In case of at_args being empty, as might be the case by
// passing empty argument array to execve under some platforms,
@@ -722,7 +717,7 @@ fn main_args(at_args: &[String]) -> MainResult {
// the compiler with @empty_file as argv[0] and no more arguments.
let at_args = at_args.get(1..).unwrap_or_default();
- let args = rustc_driver::args::arg_expand_all(at_args);
+ let args = rustc_driver::args::arg_expand_all(handler, at_args);
let mut options = getopts::Options::new();
for option in opts() {
@@ -731,13 +726,13 @@ fn main_args(at_args: &[String]) -> MainResult {
let matches = match options.parse(&args) {
Ok(m) => m,
Err(err) => {
- early_error(ErrorOutputType::default(), err.to_string());
+ handler.early_error(err.to_string());
}
};
// Note that we discard any distinction between different non-zero exit
// codes from `from_matches` here.
- let (options, render_options) = match config::Options::from_matches(&matches, args) {
+ let (options, render_options) = match config::Options::from_matches(handler, &matches, args) {
Ok(opts) => opts,
Err(code) => {
return if code == 0 {
@@ -765,7 +760,7 @@ fn main_args(at_args: &[String]) -> MainResult {
(false, true) => {
let input = options.input.clone();
let edition = options.edition;
- let config = core::create_config(options, &render_options);
+ let config = core::create_config(handler, options, &render_options);
// `markdown::render` can invoke `doctest::make_test`, which
// requires session globals and a thread pool, so we use
@@ -798,7 +793,7 @@ fn main_args(at_args: &[String]) -> MainResult {
let scrape_examples_options = options.scrape_examples_options.clone();
let bin_crate = options.bin_crate;
- let config = core::create_config(options, &render_options);
+ let config = core::create_config(handler, options, &render_options);
interface::run_compiler(config, |compiler| {
let sess = compiler.session();
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 061a572c4..0dd9e590b 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -205,7 +205,7 @@ impl UrlFragment {
&UrlFragment::Item(def_id) => {
let kind = match tcx.def_kind(def_id) {
DefKind::AssocFn => {
- if tcx.impl_defaultness(def_id).has_value() {
+ if tcx.defaultness(def_id).has_value() {
"method."
} else {
"tymethod."
@@ -398,6 +398,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
.doc_link_resolutions(module_id)
.get(&(Symbol::intern(path_str), ns))
.copied()
+ // NOTE: do not remove this panic! Missing links should be recorded as `Res::Err`; if
+ // `doc_link_resolutions` is missing a `path_str`, that means that there are valid links
+ // that are being missed. To fix the ICE, change
+ // `rustc_resolve::rustdoc::attrs_to_preprocessed_links` to cache the link.
.unwrap_or_else(|| panic!("no resolution for {:?} {:?} {:?}", path_str, ns, module_id))
.and_then(|res| res.try_into().ok())
.or_else(|| resolve_primitive(path_str, ns));
@@ -842,7 +846,7 @@ impl PreprocessingError {
match self {
PreprocessingError::MultipleAnchors => report_multiple_anchors(cx, diag_info),
PreprocessingError::Disambiguator(range, msg) => {
- disambiguator_error(cx, diag_info, range.clone(), msg.as_str())
+ disambiguator_error(cx, diag_info, range.clone(), msg.clone())
}
PreprocessingError::MalformedGenerics(err, path_str) => {
report_malformed_generics(cx, diag_info, *err, path_str)
diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs
index a10d5fdb4..e9cee92d2 100644
--- a/src/librustdoc/passes/lint/bare_urls.rs
+++ b/src/librustdoc/passes/lint/bare_urls.rs
@@ -20,19 +20,20 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item) {
};
let dox = item.doc_value();
if !dox.is_empty() {
- let report_diag = |cx: &DocContext<'_>, msg: &str, url: &str, range: Range<usize>| {
- let sp = source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs)
- .unwrap_or_else(|| item.attr_span(cx.tcx));
- cx.tcx.struct_span_lint_hir(crate::lint::BARE_URLS, hir_id, sp, msg, |lint| {
- lint.note("bare URLs are not automatically turned into clickable links")
- .span_suggestion(
- sp,
- "use an automatic link instead",
- format!("<{}>", url),
- Applicability::MachineApplicable,
- )
- });
- };
+ let report_diag =
+ |cx: &DocContext<'_>, msg: &'static str, url: &str, range: Range<usize>| {
+ let sp = source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs)
+ .unwrap_or_else(|| item.attr_span(cx.tcx));
+ cx.tcx.struct_span_lint_hir(crate::lint::BARE_URLS, hir_id, sp, msg, |lint| {
+ lint.note("bare URLs are not automatically turned into clickable links")
+ .span_suggestion(
+ sp,
+ "use an automatic link instead",
+ format!("<{}>", url),
+ Applicability::MachineApplicable,
+ )
+ });
+ };
let mut p = Parser::new_ext(&dox, main_body_opts()).into_offset_iter();
@@ -72,7 +73,7 @@ fn find_raw_urls(
cx: &DocContext<'_>,
text: &str,
range: Range<usize>,
- f: &impl Fn(&DocContext<'_>, &str, &str, Range<usize>),
+ f: &impl Fn(&DocContext<'_>, &'static str, &str, Range<usize>),
) {
trace!("looking for raw urls in {}", text);
// For now, we only check "full" URLs (meaning, starting with "http://" or "https://").
diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs
index f0403647a..5273f52bc 100644
--- a/src/librustdoc/passes/lint/html_tags.rs
+++ b/src/librustdoc/passes/lint/html_tags.rs
@@ -17,90 +17,96 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
else { return };
let dox = item.doc_value();
if !dox.is_empty() {
- let report_diag = |msg: &str, range: &Range<usize>, is_open_tag: bool| {
+ let report_diag = |msg: String, range: &Range<usize>, is_open_tag: bool| {
let sp = match source_span_for_markdown_range(tcx, &dox, range, &item.attrs) {
Some(sp) => sp,
None => item.attr_span(tcx),
};
- tcx.struct_span_lint_hir(crate::lint::INVALID_HTML_TAGS, hir_id, sp, msg, |lint| {
- use rustc_lint_defs::Applicability;
- // If a tag looks like `<this>`, it might actually be a generic.
- // We don't try to detect stuff `<like, this>` because that's not valid HTML,
- // and we don't try to detect stuff `<like this>` because that's not valid Rust.
- let mut generics_end = range.end;
- if let Some(Some(mut generics_start)) = (is_open_tag
- && dox[..generics_end].ends_with('>'))
- .then(|| extract_path_backwards(&dox, range.start))
- {
- while generics_start != 0
- && generics_end < dox.len()
- && dox.as_bytes()[generics_start - 1] == b'<'
- && dox.as_bytes()[generics_end] == b'>'
+ tcx.struct_span_lint_hir(
+ crate::lint::INVALID_HTML_TAGS,
+ hir_id,
+ sp,
+ msg.to_string(),
+ |lint| {
+ use rustc_lint_defs::Applicability;
+ // If a tag looks like `<this>`, it might actually be a generic.
+ // We don't try to detect stuff `<like, this>` because that's not valid HTML,
+ // and we don't try to detect stuff `<like this>` because that's not valid Rust.
+ let mut generics_end = range.end;
+ if let Some(Some(mut generics_start)) = (is_open_tag
+ && dox[..generics_end].ends_with('>'))
+ .then(|| extract_path_backwards(&dox, range.start))
{
- generics_end += 1;
- generics_start -= 1;
- if let Some(new_start) = extract_path_backwards(&dox, generics_start) {
- generics_start = new_start;
+ while generics_start != 0
+ && generics_end < dox.len()
+ && dox.as_bytes()[generics_start - 1] == b'<'
+ && dox.as_bytes()[generics_end] == b'>'
+ {
+ generics_end += 1;
+ generics_start -= 1;
+ if let Some(new_start) = extract_path_backwards(&dox, generics_start) {
+ generics_start = new_start;
+ }
+ if let Some(new_end) = extract_path_forward(&dox, generics_end) {
+ generics_end = new_end;
+ }
}
if let Some(new_end) = extract_path_forward(&dox, generics_end) {
generics_end = new_end;
}
+ let generics_sp = match source_span_for_markdown_range(
+ tcx,
+ &dox,
+ &(generics_start..generics_end),
+ &item.attrs,
+ ) {
+ Some(sp) => sp,
+ None => item.attr_span(tcx),
+ };
+ // Sometimes, we only extract part of a path. For example, consider this:
+ //
+ // <[u32] as IntoIter<u32>>::Item
+ // ^^^^^ unclosed HTML tag `u32`
+ //
+ // We don't have any code for parsing fully-qualified trait paths.
+ // In theory, we could add it, but doing it correctly would require
+ // parsing the entire path grammar, which is problematic because of
+ // overlap between the path grammar and Markdown.
+ //
+ // The example above shows that ambiguity. Is `[u32]` intended to be an
+ // intra-doc link to the u32 primitive, or is it intended to be a slice?
+ //
+ // If the below conditional were removed, we would suggest this, which is
+ // not what the user probably wants.
+ //
+ // <[u32] as `IntoIter<u32>`>::Item
+ //
+ // We know that the user actually wants to wrap the whole thing in a code
+ // block, but the only reason we know that is because `u32` does not, in
+ // fact, implement IntoIter. If the example looks like this:
+ //
+ // <[Vec<i32>] as IntoIter<i32>::Item
+ //
+ // The ideal fix would be significantly different.
+ if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<')
+ || (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>')
+ {
+ return lint;
+ }
+ // multipart form is chosen here because ``Vec<i32>`` would be confusing.
+ lint.multipart_suggestion(
+ "try marking as source code",
+ vec![
+ (generics_sp.shrink_to_lo(), String::from("`")),
+ (generics_sp.shrink_to_hi(), String::from("`")),
+ ],
+ Applicability::MaybeIncorrect,
+ );
}
- if let Some(new_end) = extract_path_forward(&dox, generics_end) {
- generics_end = new_end;
- }
- let generics_sp = match source_span_for_markdown_range(
- tcx,
- &dox,
- &(generics_start..generics_end),
- &item.attrs,
- ) {
- Some(sp) => sp,
- None => item.attr_span(tcx),
- };
- // Sometimes, we only extract part of a path. For example, consider this:
- //
- // <[u32] as IntoIter<u32>>::Item
- // ^^^^^ unclosed HTML tag `u32`
- //
- // We don't have any code for parsing fully-qualified trait paths.
- // In theory, we could add it, but doing it correctly would require
- // parsing the entire path grammar, which is problematic because of
- // overlap between the path grammar and Markdown.
- //
- // The example above shows that ambiguity. Is `[u32]` intended to be an
- // intra-doc link to the u32 primitive, or is it intended to be a slice?
- //
- // If the below conditional were removed, we would suggest this, which is
- // not what the user probably wants.
- //
- // <[u32] as `IntoIter<u32>`>::Item
- //
- // We know that the user actually wants to wrap the whole thing in a code
- // block, but the only reason we know that is because `u32` does not, in
- // fact, implement IntoIter. If the example looks like this:
- //
- // <[Vec<i32>] as IntoIter<i32>::Item
- //
- // The ideal fix would be significantly different.
- if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<')
- || (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>')
- {
- return lint;
- }
- // multipart form is chosen here because ``Vec<i32>`` would be confusing.
- lint.multipart_suggestion(
- "try marking as source code",
- vec![
- (generics_sp.shrink_to_lo(), String::from("`")),
- (generics_sp.shrink_to_hi(), String::from("`")),
- ],
- Applicability::MaybeIncorrect,
- );
- }
- lint
- });
+ lint
+ },
+ );
};
let mut tags = Vec::new();
@@ -147,11 +153,11 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
let t = t.to_lowercase();
!ALLOWED_UNCLOSED.contains(&t.as_str())
}) {
- report_diag(&format!("unclosed HTML tag `{}`", tag), range, true);
+ report_diag(format!("unclosed HTML tag `{}`", tag), range, true);
}
if let Some(range) = is_in_comment {
- report_diag("Unclosed HTML comment", &range, false);
+ report_diag("Unclosed HTML comment".to_string(), &range, false);
}
}
}
@@ -165,7 +171,7 @@ fn drop_tag(
tags: &mut Vec<(String, Range<usize>)>,
tag_name: String,
range: Range<usize>,
- f: &impl Fn(&str, &Range<usize>, bool),
+ f: &impl Fn(String, &Range<usize>, bool),
) {
let tag_name_low = tag_name.to_lowercase();
if let Some(pos) = tags.iter().rposition(|(t, _)| t.to_lowercase() == tag_name_low) {
@@ -186,14 +192,14 @@ fn drop_tag(
// `tags` is used as a queue, meaning that everything after `pos` is included inside it.
// So `<h2><h3></h2>` will look like `["h2", "h3"]`. So when closing `h2`, we will still
// have `h3`, meaning the tag wasn't closed as it should have.
- f(&format!("unclosed HTML tag `{}`", last_tag_name), &last_tag_span, true);
+ f(format!("unclosed HTML tag `{}`", last_tag_name), &last_tag_span, true);
}
// Remove the `tag_name` that was originally closed
tags.pop();
} else {
// It can happen for example in this case: `<h2></script></h2>` (the `h2` tag isn't required
// but it helps for the visualization).
- f(&format!("unopened HTML tag `{}`", tag_name), &range, false);
+ f(format!("unopened HTML tag `{}`", tag_name), &range, false);
}
}
@@ -261,7 +267,7 @@ fn extract_html_tag(
range: &Range<usize>,
start_pos: usize,
iter: &mut Peekable<CharIndices<'_>>,
- f: &impl Fn(&str, &Range<usize>, bool),
+ f: &impl Fn(String, &Range<usize>, bool),
) {
let mut tag_name = String::new();
let mut is_closing = false;
@@ -347,7 +353,7 @@ fn extract_html_tag(
if let Some(quote_pos) = quote_pos {
let qr = Range { start: quote_pos, end: quote_pos };
f(
- &format!("unclosed quoted HTML attribute on tag `{}`", tag_name),
+ format!("unclosed quoted HTML attribute on tag `{}`", tag_name),
&qr,
false,
);
@@ -360,7 +366,7 @@ fn extract_html_tag(
at == "svg" || at == "math"
});
if !valid {
- f(&format!("invalid self-closing HTML tag `{}`", tag_name), &r, false);
+ f(format!("invalid self-closing HTML tag `{}`", tag_name), &r, false);
}
} else {
tags.push((tag_name, r));
@@ -378,7 +384,7 @@ fn extract_tags(
text: &str,
range: Range<usize>,
is_in_comment: &mut Option<Range<usize>>,
- f: &impl Fn(&str, &Range<usize>, bool),
+ f: &impl Fn(String, &Range<usize>, bool),
) {
let mut iter = text.char_indices().peekable();
diff --git a/src/librustdoc/passes/lint/unescaped_backticks.rs b/src/librustdoc/passes/lint/unescaped_backticks.rs
index 865212205..256958d71 100644
--- a/src/librustdoc/passes/lint/unescaped_backticks.rs
+++ b/src/librustdoc/passes/lint/unescaped_backticks.rs
@@ -373,7 +373,7 @@ fn suggest_insertion(
lint: &mut DiagnosticBuilder<'_, ()>,
insert_index: usize,
suggestion: char,
- message: &str,
+ message: &'static str,
) {
/// Maximum bytes of context to show around the insertion.
const CONTEXT_MAX_LEN: usize = 80;
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index 972b0c5ec..e2e38d3e7 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -6,7 +6,7 @@ use rustc_span::symbol::sym;
use std::mem;
use crate::clean;
-use crate::clean::{Item, ItemIdSet, NestedAttributesExt};
+use crate::clean::{Item, ItemIdSet};
use crate::core::DocContext;
use crate::fold::{strip_item, DocFolder};
use crate::passes::{ImplStripper, Pass};
@@ -85,7 +85,7 @@ impl<'a, 'tcx> Stripper<'a, 'tcx> {
impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
- let has_doc_hidden = i.attrs.lists(sym::doc).has_word(sym::hidden);
+ let has_doc_hidden = i.is_doc_hidden();
let is_impl_or_exported_macro = match *i.kind {
clean::ImplItem(..) => true,
// If the macro has the `#[macro_export]` attribute, it means it's accessible at the
diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs
index 73fc26a6b..90c361d9d 100644
--- a/src/librustdoc/passes/stripper.rs
+++ b/src/librustdoc/passes/stripper.rs
@@ -1,10 +1,9 @@
//! A collection of utility functions for the `strip_*` passes.
use rustc_hir::def_id::DefId;
use rustc_middle::ty::{TyCtxt, Visibility};
-use rustc_span::symbol::sym;
use std::mem;
-use crate::clean::{self, Item, ItemId, ItemIdSet, NestedAttributesExt};
+use crate::clean::{self, Item, ItemId, ItemIdSet};
use crate::fold::{strip_item, DocFolder};
use crate::formats::cache::Cache;
use crate::visit_lib::RustdocEffectiveVisibilities;
@@ -163,7 +162,7 @@ impl<'a> ImplStripper<'a, '_> {
// If the "for" item is exported and the impl block isn't `#[doc(hidden)]`, then we
// need to keep it.
self.cache.effective_visibilities.is_exported(self.tcx, for_def_id)
- && !item.attrs.lists(sym::doc).has_word(sym::hidden)
+ && !item.is_doc_hidden()
} else {
false
}
@@ -240,7 +239,7 @@ impl<'tcx> ImportStripper<'tcx> {
// FIXME: This should be handled the same way as for HTML output.
imp.imported_item_is_doc_hidden(self.tcx)
} else {
- i.attrs.lists(sym::doc).has_word(sym::hidden)
+ i.is_doc_hidden()
}
}
}
@@ -249,7 +248,7 @@ impl<'tcx> DocFolder for ImportStripper<'tcx> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
match *i.kind {
clean::ImportItem(imp) if self.import_should_be_hidden(&i, &imp) => None,
- clean::ImportItem(_) if i.attrs.lists(sym::doc).has_word(sym::hidden) => None,
+ clean::ImportItem(_) if i.is_doc_hidden() => None,
clean::ExternCrateItem { .. } | clean::ImportItem(..)
if i.visibility(self.tcx) != Some(Visibility::Public) =>
{
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 6b7ad4cf2..fcf591a93 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -14,7 +14,7 @@ use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::Span;
-use std::{iter, mem};
+use std::mem;
use crate::clean::{cfg::Cfg, reexport_chain, AttributesExt, NestedAttributesExt};
use crate::core;
@@ -147,9 +147,9 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
// `#[macro_export] macro_rules!` items are reexported at the top level of the
// crate, regardless of where they're defined. We want to document the
- // top level rexport of the macro, not its original definition, since
- // the rexport defines the path that a user will actually see. Accordingly,
- // we add the rexport as an item here, and then skip over the original
+ // top level re-export of the macro, not its original definition, since
+ // the re-export defines the path that a user will actually see. Accordingly,
+ // we add the re-export as an item here, and then skip over the original
// definition in `visit_item()` below.
//
// We also skip `#[macro_export] macro_rules!` that have already been inserted,
@@ -246,7 +246,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
glob: bool,
please_inline: bool,
) -> bool {
- debug!("maybe_inline_local res: {:?}", res);
+ debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
if renamed == Some(kw::Underscore) {
// We never inline `_` reexports.
@@ -267,6 +267,10 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
let is_no_inline = use_attrs.lists(sym::doc).has_word(sym::no_inline)
|| use_attrs.lists(sym::doc).has_word(sym::hidden);
+ if is_no_inline {
+ return false;
+ }
+
// For cross-crate impl inlining we need to know whether items are
// reachable in documentation -- a previously unreachable item can be
// made reachable by cross-crate inlining which we're checking here.
@@ -281,31 +285,27 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
};
let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did);
- let is_hidden = inherits_doc_hidden(tcx, res_did, None);
-
- // Only inline if requested or if the item would otherwise be stripped.
- if (!please_inline && !is_private && !is_hidden) || is_no_inline {
- return false;
- }
-
- if !please_inline &&
- let Some(item_def_id) = reexport_chain(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(tcx, item_def_id.to_def_id()) &&
- !inherits_doc_hidden(tcx, item_def_id, None)
- {
- // The imported item is public and not `doc(hidden)` so no need to inline it.
- return false;
+ let is_hidden = tcx.is_doc_hidden(ori_res_did);
+ let item = tcx.hir().get_by_def_id(res_did);
+
+ if !please_inline {
+ let inherits_hidden = inherits_doc_hidden(tcx, res_did, None);
+ // Only inline if requested or if the item would otherwise be stripped.
+ if (!is_private && !inherits_hidden) || (
+ is_hidden &&
+ // If it's a doc hidden module, we need to keep it in case some of its inner items
+ // are re-exported.
+ !matches!(item, Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_), .. }))
+ ) ||
+ // The imported item is public and not `doc(hidden)` so no need to inline it.
+ self.reexport_public_and_not_hidden(def_id, res_did)
+ {
+ return false;
+ }
}
let is_bang_macro = matches!(
- tcx.hir().get_by_def_id(res_did),
+ item,
Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, MacroKind::Bang), .. })
);
@@ -313,16 +313,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
return false;
}
- let ret = match tcx.hir().get_by_def_id(res_did) {
+ let inlined = match tcx.hir().get_by_def_id(res_did) {
// Bang macros are handled a bit on their because of how they are handled by the
// compiler. If they have `#[doc(hidden)]` and the re-export doesn't have
// `#[doc(inline)]`, then we don't inline it.
- Node::Item(_)
- if is_bang_macro
- && !please_inline
- && renamed.is_some()
- && self.cx.tcx.is_doc_hidden(ori_res_did) =>
- {
+ Node::Item(_) if is_bang_macro && !please_inline && renamed.is_some() && is_hidden => {
return false;
}
Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => {
@@ -349,7 +344,32 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
_ => false,
};
self.view_item_stack.remove(&res_did);
- ret
+ if inlined {
+ self.cx.cache.inlined_items.insert(res_did.to_def_id());
+ }
+ inlined
+ }
+
+ /// Returns `true` if the item is visible, meaning it's not `#[doc(hidden)]` or private.
+ ///
+ /// This function takes into account the entire re-export `use` chain, so it needs the
+ /// ID of the "leaf" `use` and the ID of the "root" item.
+ fn reexport_public_and_not_hidden(
+ &self,
+ import_def_id: LocalDefId,
+ target_def_id: LocalDefId,
+ ) -> bool {
+ let tcx = self.cx.tcx;
+ let item_def_id = reexport_chain(tcx, import_def_id, target_def_id)
+ .iter()
+ .flat_map(|reexport| reexport.id())
+ .map(|id| id.expect_local())
+ .nth(1)
+ .unwrap_or(target_def_id);
+ item_def_id != import_def_id
+ && self.cx.cache.effective_visibilities.is_directly_public(tcx, item_def_id.to_def_id())
+ && !tcx.is_doc_hidden(item_def_id)
+ && !inherits_doc_hidden(tcx, item_def_id, None)
}
#[inline]
@@ -455,6 +475,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
is_glob,
please_inline,
) {
+ debug!("Inlining {:?}", item.owner_id.def_id);
continue;
}
}