Auto merge of #82777 - GuillaumeGomez:rollup-etcsupl, r=GuillaumeGomez
Rollup of 5 pull requests Successful merges: - #76716 (Don't warn for `missing_doc_examples` when item is #[doc(hidden)]) - #82088 (Shorten html::render) - #82690 (Update rustdoc documentation) - #82752 (Add a regression test for issue-81712) - #82765 (Fix polymorphization ICE on associated types in trait decls using const generics in bounds) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
8ccc89bc31
19 changed files with 2827 additions and 2751 deletions
|
@ -438,18 +438,6 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn optimized_mir_or_const_arg_mir(
|
|
||||||
self,
|
|
||||||
def: ty::WithOptConstParam<DefId>,
|
|
||||||
) -> &'tcx Body<'tcx> {
|
|
||||||
if let Some((did, param_did)) = def.as_const_arg() {
|
|
||||||
self.mir_for_ctfe_of_const_arg((did, param_did))
|
|
||||||
} else {
|
|
||||||
self.optimized_mir(def.did)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn mir_for_ctfe_opt_const_arg(self, def: ty::WithOptConstParam<DefId>) -> &'tcx Body<'tcx> {
|
pub fn mir_for_ctfe_opt_const_arg(self, def: ty::WithOptConstParam<DefId>) -> &'tcx Body<'tcx> {
|
||||||
if let Some((did, param_did)) = def.as_const_arg() {
|
if let Some((did, param_did)) = def.as_const_arg() {
|
||||||
|
|
|
@ -499,7 +499,7 @@ impl<'tcx> Instance<'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new `Instance` where generic parameters in `instance.substs` are replaced by
|
/// Returns a new `Instance` where generic parameters in `instance.substs` are replaced by
|
||||||
/// identify parameters if they are determined to be unused in `instance.def`.
|
/// identity parameters if they are determined to be unused in `instance.def`.
|
||||||
pub fn polymorphize(self, tcx: TyCtxt<'tcx>) -> Self {
|
pub fn polymorphize(self, tcx: TyCtxt<'tcx>) -> Self {
|
||||||
debug!("polymorphize: running polymorphization analysis");
|
debug!("polymorphize: running polymorphization analysis");
|
||||||
if !tcx.sess.opts.debugging_opts.polymorphize {
|
if !tcx.sess.opts.debugging_opts.polymorphize {
|
||||||
|
|
|
@ -2963,7 +2963,10 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||||
| DefKind::AnonConst => self.mir_for_ctfe_opt_const_arg(def),
|
| DefKind::AnonConst => self.mir_for_ctfe_opt_const_arg(def),
|
||||||
// If the caller wants `mir_for_ctfe` of a function they should not be using
|
// If the caller wants `mir_for_ctfe` of a function they should not be using
|
||||||
// `instance_mir`, so we'll assume const fn also wants the optimized version.
|
// `instance_mir`, so we'll assume const fn also wants the optimized version.
|
||||||
_ => self.optimized_mir_or_const_arg_mir(def),
|
_ => {
|
||||||
|
assert_eq!(def.const_param_did, None);
|
||||||
|
self.optimized_mir(def.did)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ty::InstanceDef::VtableShim(..)
|
ty::InstanceDef::VtableShim(..)
|
||||||
| ty::InstanceDef::ReifyShim(..)
|
| ty::InstanceDef::ReifyShim(..)
|
||||||
|
|
|
@ -30,9 +30,8 @@ pub fn provide(providers: &mut Providers) {
|
||||||
/// Determine which generic parameters are used by the function/method/closure represented by
|
/// Determine which generic parameters are used by the function/method/closure represented by
|
||||||
/// `def_id`. Returns a bitset where bits representing unused parameters are set (`is_empty`
|
/// `def_id`. Returns a bitset where bits representing unused parameters are set (`is_empty`
|
||||||
/// indicates all parameters are used).
|
/// indicates all parameters are used).
|
||||||
|
#[instrument(skip(tcx))]
|
||||||
fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
||||||
debug!("unused_generic_params({:?})", def_id);
|
|
||||||
|
|
||||||
if !tcx.sess.opts.debugging_opts.polymorphize {
|
if !tcx.sess.opts.debugging_opts.polymorphize {
|
||||||
// If polymorphization disabled, then all parameters are used.
|
// If polymorphization disabled, then all parameters are used.
|
||||||
return FiniteBitSet::new_empty();
|
return FiniteBitSet::new_empty();
|
||||||
|
@ -46,7 +45,7 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let generics = tcx.generics_of(def_id);
|
let generics = tcx.generics_of(def_id);
|
||||||
debug!("unused_generic_params: generics={:?}", generics);
|
debug!(?generics);
|
||||||
|
|
||||||
// Exit early when there are no parameters to be unused.
|
// Exit early when there are no parameters to be unused.
|
||||||
if generics.count() == 0 {
|
if generics.count() == 0 {
|
||||||
|
@ -57,11 +56,11 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
||||||
let context = tcx.hir().body_const_context(def_id.expect_local());
|
let context = tcx.hir().body_const_context(def_id.expect_local());
|
||||||
match context {
|
match context {
|
||||||
Some(ConstContext::ConstFn) | None if !tcx.is_mir_available(def_id) => {
|
Some(ConstContext::ConstFn) | None if !tcx.is_mir_available(def_id) => {
|
||||||
debug!("unused_generic_params: (no mir available) def_id={:?}", def_id);
|
debug!("no mir available");
|
||||||
return FiniteBitSet::new_empty();
|
return FiniteBitSet::new_empty();
|
||||||
}
|
}
|
||||||
Some(_) if !tcx.is_ctfe_mir_available(def_id) => {
|
Some(_) if !tcx.is_ctfe_mir_available(def_id) => {
|
||||||
debug!("unused_generic_params: (no ctfe mir available) def_id={:?}", def_id);
|
debug!("no ctfe mir available");
|
||||||
return FiniteBitSet::new_empty();
|
return FiniteBitSet::new_empty();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -72,9 +71,9 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
||||||
generics.count().try_into().expect("more generic parameters than can fit into a `u32`");
|
generics.count().try_into().expect("more generic parameters than can fit into a `u32`");
|
||||||
let mut unused_parameters = FiniteBitSet::<u32>::new_empty();
|
let mut unused_parameters = FiniteBitSet::<u32>::new_empty();
|
||||||
unused_parameters.set_range(0..generics_count);
|
unused_parameters.set_range(0..generics_count);
|
||||||
debug!("unused_generic_params: (start) unused_parameters={:?}", unused_parameters);
|
debug!(?unused_parameters, "(start)");
|
||||||
mark_used_by_default_parameters(tcx, def_id, generics, &mut unused_parameters);
|
mark_used_by_default_parameters(tcx, def_id, generics, &mut unused_parameters);
|
||||||
debug!("unused_generic_params: (after default) unused_parameters={:?}", unused_parameters);
|
debug!(?unused_parameters, "(after default)");
|
||||||
|
|
||||||
// Visit MIR and accumululate used generic parameters.
|
// Visit MIR and accumululate used generic parameters.
|
||||||
let body = match context {
|
let body = match context {
|
||||||
|
@ -85,10 +84,10 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
||||||
};
|
};
|
||||||
let mut vis = MarkUsedGenericParams { tcx, def_id, unused_parameters: &mut unused_parameters };
|
let mut vis = MarkUsedGenericParams { tcx, def_id, unused_parameters: &mut unused_parameters };
|
||||||
vis.visit_body(body);
|
vis.visit_body(body);
|
||||||
debug!("unused_generic_params: (after visitor) unused_parameters={:?}", unused_parameters);
|
debug!(?unused_parameters, "(after visitor)");
|
||||||
|
|
||||||
mark_used_by_predicates(tcx, def_id, &mut unused_parameters);
|
mark_used_by_predicates(tcx, def_id, &mut unused_parameters);
|
||||||
debug!("unused_generic_params: (end) unused_parameters={:?}", unused_parameters);
|
debug!(?unused_parameters, "(end)");
|
||||||
|
|
||||||
// Emit errors for debugging and testing if enabled.
|
// Emit errors for debugging and testing if enabled.
|
||||||
if !unused_parameters.is_empty() {
|
if !unused_parameters.is_empty() {
|
||||||
|
@ -101,24 +100,55 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
||||||
/// Some parameters are considered used-by-default, such as non-generic parameters and the dummy
|
/// Some parameters are considered used-by-default, such as non-generic parameters and the dummy
|
||||||
/// generic parameters from closures, this function marks them as used. `leaf_is_closure` should
|
/// generic parameters from closures, this function marks them as used. `leaf_is_closure` should
|
||||||
/// be `true` if the item that `unused_generic_params` was invoked on is a closure.
|
/// be `true` if the item that `unused_generic_params` was invoked on is a closure.
|
||||||
|
#[instrument(skip(tcx, def_id, generics, unused_parameters))]
|
||||||
fn mark_used_by_default_parameters<'tcx>(
|
fn mark_used_by_default_parameters<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
def_id: DefId,
|
def_id: DefId,
|
||||||
generics: &'tcx ty::Generics,
|
generics: &'tcx ty::Generics,
|
||||||
unused_parameters: &mut FiniteBitSet<u32>,
|
unused_parameters: &mut FiniteBitSet<u32>,
|
||||||
) {
|
) {
|
||||||
if !tcx.is_trait(def_id) && (tcx.is_closure(def_id) || tcx.type_of(def_id).is_generator()) {
|
match tcx.def_kind(def_id) {
|
||||||
for param in &generics.params {
|
DefKind::Closure | DefKind::Generator => {
|
||||||
debug!("mark_used_by_default_parameters: (closure/gen) param={:?}", param);
|
for param in &generics.params {
|
||||||
unused_parameters.clear(param.index);
|
debug!(?param, "(closure/gen)");
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for param in &generics.params {
|
|
||||||
debug!("mark_used_by_default_parameters: (other) param={:?}", param);
|
|
||||||
if let ty::GenericParamDefKind::Lifetime = param.kind {
|
|
||||||
unused_parameters.clear(param.index);
|
unused_parameters.clear(param.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DefKind::Mod
|
||||||
|
| DefKind::Struct
|
||||||
|
| DefKind::Union
|
||||||
|
| DefKind::Enum
|
||||||
|
| DefKind::Variant
|
||||||
|
| DefKind::Trait
|
||||||
|
| DefKind::TyAlias
|
||||||
|
| DefKind::ForeignTy
|
||||||
|
| DefKind::TraitAlias
|
||||||
|
| DefKind::AssocTy
|
||||||
|
| DefKind::TyParam
|
||||||
|
| DefKind::Fn
|
||||||
|
| DefKind::Const
|
||||||
|
| DefKind::ConstParam
|
||||||
|
| DefKind::Static
|
||||||
|
| DefKind::Ctor(_, _)
|
||||||
|
| DefKind::AssocFn
|
||||||
|
| DefKind::AssocConst
|
||||||
|
| DefKind::Macro(_)
|
||||||
|
| DefKind::ExternCrate
|
||||||
|
| DefKind::Use
|
||||||
|
| DefKind::ForeignMod
|
||||||
|
| DefKind::AnonConst
|
||||||
|
| DefKind::OpaqueTy
|
||||||
|
| DefKind::Field
|
||||||
|
| DefKind::LifetimeParam
|
||||||
|
| DefKind::GlobalAsm
|
||||||
|
| DefKind::Impl => {
|
||||||
|
for param in &generics.params {
|
||||||
|
debug!(?param, "(other)");
|
||||||
|
if let ty::GenericParamDefKind::Lifetime = param.kind {
|
||||||
|
unused_parameters.clear(param.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(parent) = generics.parent {
|
if let Some(parent) = generics.parent {
|
||||||
|
@ -128,6 +158,7 @@ fn mark_used_by_default_parameters<'tcx>(
|
||||||
|
|
||||||
/// Search the predicates on used generic parameters for any unused generic parameters, and mark
|
/// Search the predicates on used generic parameters for any unused generic parameters, and mark
|
||||||
/// those as used.
|
/// those as used.
|
||||||
|
#[instrument(skip(tcx, def_id))]
|
||||||
fn mark_used_by_predicates<'tcx>(
|
fn mark_used_by_predicates<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
def_id: DefId,
|
def_id: DefId,
|
||||||
|
@ -135,16 +166,12 @@ fn mark_used_by_predicates<'tcx>(
|
||||||
) {
|
) {
|
||||||
let def_id = tcx.closure_base_def_id(def_id);
|
let def_id = tcx.closure_base_def_id(def_id);
|
||||||
let predicates = tcx.explicit_predicates_of(def_id);
|
let predicates = tcx.explicit_predicates_of(def_id);
|
||||||
debug!("mark_used_by_predicates: predicates_of={:?}", predicates);
|
|
||||||
|
|
||||||
let mut current_unused_parameters = FiniteBitSet::new_empty();
|
let mut current_unused_parameters = FiniteBitSet::new_empty();
|
||||||
// Run to a fixed point to support `where T: Trait<U>, U: Trait<V>`, starting with an empty
|
// Run to a fixed point to support `where T: Trait<U>, U: Trait<V>`, starting with an empty
|
||||||
// bit set so that this is skipped if all parameters are already used.
|
// bit set so that this is skipped if all parameters are already used.
|
||||||
while current_unused_parameters != *unused_parameters {
|
while current_unused_parameters != *unused_parameters {
|
||||||
debug!(
|
debug!(?current_unused_parameters, ?unused_parameters);
|
||||||
"mark_used_by_predicates: current_unused_parameters={:?} = unused_parameters={:?}",
|
|
||||||
current_unused_parameters, unused_parameters
|
|
||||||
);
|
|
||||||
current_unused_parameters = *unused_parameters;
|
current_unused_parameters = *unused_parameters;
|
||||||
|
|
||||||
for (predicate, _) in predicates.predicates {
|
for (predicate, _) in predicates.predicates {
|
||||||
|
@ -169,13 +196,13 @@ fn mark_used_by_predicates<'tcx>(
|
||||||
|
|
||||||
/// Emit errors for the function annotated by `#[rustc_polymorphize_error]`, labelling each generic
|
/// Emit errors for the function annotated by `#[rustc_polymorphize_error]`, labelling each generic
|
||||||
/// parameter which was unused.
|
/// parameter which was unused.
|
||||||
|
#[instrument(skip(tcx, generics))]
|
||||||
fn emit_unused_generic_params_error<'tcx>(
|
fn emit_unused_generic_params_error<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
def_id: DefId,
|
def_id: DefId,
|
||||||
generics: &'tcx ty::Generics,
|
generics: &'tcx ty::Generics,
|
||||||
unused_parameters: &FiniteBitSet<u32>,
|
unused_parameters: &FiniteBitSet<u32>,
|
||||||
) {
|
) {
|
||||||
debug!("emit_unused_generic_params_error: def_id={:?}", def_id);
|
|
||||||
let base_def_id = tcx.closure_base_def_id(def_id);
|
let base_def_id = tcx.closure_base_def_id(def_id);
|
||||||
if !tcx
|
if !tcx
|
||||||
.get_attrs(base_def_id)
|
.get_attrs(base_def_id)
|
||||||
|
@ -185,7 +212,6 @@ fn emit_unused_generic_params_error<'tcx>(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("emit_unused_generic_params_error: unused_parameters={:?}", unused_parameters);
|
|
||||||
let fn_span = match tcx.opt_item_name(def_id) {
|
let fn_span = match tcx.opt_item_name(def_id) {
|
||||||
Some(ident) => ident.span,
|
Some(ident) => ident.span,
|
||||||
_ => tcx.def_span(def_id),
|
_ => tcx.def_span(def_id),
|
||||||
|
@ -197,7 +223,7 @@ fn emit_unused_generic_params_error<'tcx>(
|
||||||
while let Some(generics) = next_generics {
|
while let Some(generics) = next_generics {
|
||||||
for param in &generics.params {
|
for param in &generics.params {
|
||||||
if unused_parameters.contains(param.index).unwrap_or(false) {
|
if unused_parameters.contains(param.index).unwrap_or(false) {
|
||||||
debug!("emit_unused_generic_params_error: param={:?}", param);
|
debug!(?param);
|
||||||
let def_span = tcx.def_span(param.def_id);
|
let def_span = tcx.def_span(param.def_id);
|
||||||
err.span_label(def_span, &format!("generic parameter `{}` is unused", param.name));
|
err.span_label(def_span, &format!("generic parameter `{}` is unused", param.name));
|
||||||
}
|
}
|
||||||
|
@ -219,25 +245,23 @@ struct MarkUsedGenericParams<'a, 'tcx> {
|
||||||
impl<'a, 'tcx> MarkUsedGenericParams<'a, 'tcx> {
|
impl<'a, 'tcx> MarkUsedGenericParams<'a, 'tcx> {
|
||||||
/// Invoke `unused_generic_params` on a body contained within the current item (e.g.
|
/// Invoke `unused_generic_params` on a body contained within the current item (e.g.
|
||||||
/// a closure, generator or constant).
|
/// a closure, generator or constant).
|
||||||
|
#[instrument(skip(self, def_id, substs))]
|
||||||
fn visit_child_body(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) {
|
fn visit_child_body(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) {
|
||||||
let unused = self.tcx.unused_generic_params(def_id);
|
let unused = self.tcx.unused_generic_params(def_id);
|
||||||
debug!(
|
debug!(?self.unused_parameters, ?unused);
|
||||||
"visit_child_body: unused_parameters={:?} unused={:?}",
|
|
||||||
self.unused_parameters, unused
|
|
||||||
);
|
|
||||||
for (i, arg) in substs.iter().enumerate() {
|
for (i, arg) in substs.iter().enumerate() {
|
||||||
let i = i.try_into().unwrap();
|
let i = i.try_into().unwrap();
|
||||||
if !unused.contains(i).unwrap_or(false) {
|
if !unused.contains(i).unwrap_or(false) {
|
||||||
arg.visit_with(self);
|
arg.visit_with(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!("visit_child_body: unused_parameters={:?}", self.unused_parameters);
|
debug!(?self.unused_parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
||||||
|
#[instrument(skip(self, local))]
|
||||||
fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) {
|
fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) {
|
||||||
debug!("visit_local_decl: local_decl={:?}", local_decl);
|
|
||||||
if local == Local::from_usize(1) {
|
if local == Local::from_usize(1) {
|
||||||
let def_kind = self.tcx.def_kind(self.def_id);
|
let def_kind = self.tcx.def_kind(self.def_id);
|
||||||
if matches!(def_kind, DefKind::Closure | DefKind::Generator) {
|
if matches!(def_kind, DefKind::Closure | DefKind::Generator) {
|
||||||
|
@ -245,7 +269,7 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
||||||
// happens because the first argument to the closure is a reference to itself and
|
// happens because the first argument to the closure is a reference to itself and
|
||||||
// that will call `visit_substs`, resulting in each generic parameter captured being
|
// that will call `visit_substs`, resulting in each generic parameter captured being
|
||||||
// considered used by default.
|
// considered used by default.
|
||||||
debug!("visit_local_decl: skipping closure substs");
|
debug!("skipping closure substs");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,15 +287,15 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
||||||
|
#[instrument(skip(self))]
|
||||||
fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow<Self::BreakTy> {
|
fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||||
debug!("visit_const: c={:?}", c);
|
|
||||||
if !c.has_param_types_or_consts() {
|
if !c.has_param_types_or_consts() {
|
||||||
return ControlFlow::CONTINUE;
|
return ControlFlow::CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
match c.val {
|
match c.val {
|
||||||
ty::ConstKind::Param(param) => {
|
ty::ConstKind::Param(param) => {
|
||||||
debug!("visit_const: param={:?}", param);
|
debug!(?param);
|
||||||
self.unused_parameters.clear(param.index);
|
self.unused_parameters.clear(param.index);
|
||||||
ControlFlow::CONTINUE
|
ControlFlow::CONTINUE
|
||||||
}
|
}
|
||||||
|
@ -296,15 +320,15 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||||
debug!("visit_ty: ty={:?}", ty);
|
|
||||||
if !ty.has_param_types_or_consts() {
|
if !ty.has_param_types_or_consts() {
|
||||||
return ControlFlow::CONTINUE;
|
return ControlFlow::CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
match *ty.kind() {
|
match *ty.kind() {
|
||||||
ty::Closure(def_id, substs) | ty::Generator(def_id, substs, ..) => {
|
ty::Closure(def_id, substs) | ty::Generator(def_id, substs, ..) => {
|
||||||
debug!("visit_ty: def_id={:?}", def_id);
|
debug!(?def_id);
|
||||||
// Avoid cycle errors with generators.
|
// Avoid cycle errors with generators.
|
||||||
if def_id == self.def_id {
|
if def_id == self.def_id {
|
||||||
return ControlFlow::CONTINUE;
|
return ControlFlow::CONTINUE;
|
||||||
|
@ -316,7 +340,7 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
||||||
ControlFlow::CONTINUE
|
ControlFlow::CONTINUE
|
||||||
}
|
}
|
||||||
ty::Param(param) => {
|
ty::Param(param) => {
|
||||||
debug!("visit_ty: param={:?}", param);
|
debug!(?param);
|
||||||
self.unused_parameters.clear(param.index);
|
self.unused_parameters.clear(param.index);
|
||||||
ControlFlow::CONTINUE
|
ControlFlow::CONTINUE
|
||||||
}
|
}
|
||||||
|
@ -333,8 +357,8 @@ struct HasUsedGenericParams<'a> {
|
||||||
impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> {
|
impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> {
|
||||||
type BreakTy = ();
|
type BreakTy = ();
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow<Self::BreakTy> {
|
fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||||
debug!("visit_const: c={:?}", c);
|
|
||||||
if !c.has_param_types_or_consts() {
|
if !c.has_param_types_or_consts() {
|
||||||
return ControlFlow::CONTINUE;
|
return ControlFlow::CONTINUE;
|
||||||
}
|
}
|
||||||
|
@ -351,8 +375,8 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||||
debug!("visit_ty: ty={:?}", ty);
|
|
||||||
if !ty.has_param_types_or_consts() {
|
if !ty.has_param_types_or_consts() {
|
||||||
return ControlFlow::CONTINUE;
|
return ControlFlow::CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
- [Documentation tests](documentation-tests.md)
|
- [Documentation tests](documentation-tests.md)
|
||||||
- [Linking to items by name](linking-to-items-by-name.md)
|
- [Linking to items by name](linking-to-items-by-name.md)
|
||||||
- [Lints](lints.md)
|
- [Lints](lints.md)
|
||||||
- [Passes](passes.md)
|
|
||||||
- [Advanced features](advanced-features.md)
|
- [Advanced features](advanced-features.md)
|
||||||
- [Unstable features](unstable-features.md)
|
- [Unstable features](unstable-features.md)
|
||||||
|
- [Passes](passes.md)
|
||||||
- [References](references.md)
|
- [References](references.md)
|
||||||
|
|
|
@ -57,23 +57,6 @@ release: 1.17.0
|
||||||
LLVM version: 3.9
|
LLVM version: 3.9
|
||||||
```
|
```
|
||||||
|
|
||||||
## `-r`/`--input-format`: input format
|
|
||||||
|
|
||||||
This flag is currently ignored; the idea is that `rustdoc` would support various
|
|
||||||
input formats, and you could specify them via this flag.
|
|
||||||
|
|
||||||
Rustdoc only supports Rust source code and Markdown input formats. If the
|
|
||||||
file ends in `.md` or `.markdown`, `rustdoc` treats it as a Markdown file.
|
|
||||||
Otherwise, it assumes that the input file is Rust.
|
|
||||||
|
|
||||||
|
|
||||||
## `-w`/`--output-format`: output format
|
|
||||||
|
|
||||||
This flag is currently ignored; the idea is that `rustdoc` would support
|
|
||||||
various output formats, and you could specify them via this flag.
|
|
||||||
|
|
||||||
Rustdoc only supports HTML output, and so this flag is redundant today.
|
|
||||||
|
|
||||||
## `-o`/`--output`: output path
|
## `-o`/`--output`: output path
|
||||||
|
|
||||||
Using this flag looks like this:
|
Using this flag looks like this:
|
||||||
|
@ -100,6 +83,25 @@ By default, `rustdoc` assumes that the name of your crate is the same name
|
||||||
as the `.rs` file. `--crate-name` lets you override this assumption with
|
as the `.rs` file. `--crate-name` lets you override this assumption with
|
||||||
whatever name you choose.
|
whatever name you choose.
|
||||||
|
|
||||||
|
## `--document-private-items`: Show items that are not public
|
||||||
|
|
||||||
|
Using this flag looks like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ rustdoc src/lib.rs --document-private-items
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, `rustdoc` only documents items that are publicly reachable.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn public() {} // this item is public and will documented
|
||||||
|
mod private { // this item is private and will not be documented
|
||||||
|
pub fn unreachable() {} // this item is public, but unreachable, so it will not be documented
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`--document-private-items` documents all items, even if they're not public.
|
||||||
|
|
||||||
## `-L`/`--library-path`: where to look for dependencies
|
## `-L`/`--library-path`: where to look for dependencies
|
||||||
|
|
||||||
Using this flag looks like this:
|
Using this flag looks like this:
|
||||||
|
@ -166,38 +168,6 @@ affect that.
|
||||||
The arguments to this flag are the same as those for the `-C` flag on rustc. Run `rustc -C help` to
|
The arguments to this flag are the same as those for the `-C` flag on rustc. Run `rustc -C help` to
|
||||||
get the full list.
|
get the full list.
|
||||||
|
|
||||||
## `--passes`: add more rustdoc passes
|
|
||||||
|
|
||||||
Using this flag looks like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ rustdoc --passes list
|
|
||||||
$ rustdoc src/lib.rs --passes strip-priv-imports
|
|
||||||
```
|
|
||||||
|
|
||||||
An argument of "list" will print a list of possible "rustdoc passes", and other
|
|
||||||
arguments will be the name of which passes to run in addition to the defaults.
|
|
||||||
|
|
||||||
For more details on passes, see [the chapter on them](passes.md).
|
|
||||||
|
|
||||||
See also `--no-defaults`.
|
|
||||||
|
|
||||||
## `--no-defaults`: don't run default passes
|
|
||||||
|
|
||||||
Using this flag looks like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ rustdoc src/lib.rs --no-defaults
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, `rustdoc` will run several passes over your code. This
|
|
||||||
removes those defaults, allowing you to use `--passes` to specify
|
|
||||||
exactly which passes you want.
|
|
||||||
|
|
||||||
For more details on passes, see [the chapter on them](passes.md).
|
|
||||||
|
|
||||||
See also `--passes`.
|
|
||||||
|
|
||||||
## `--test`: run code examples as tests
|
## `--test`: run code examples as tests
|
||||||
|
|
||||||
Using this flag looks like this:
|
Using this flag looks like this:
|
||||||
|
@ -429,3 +399,21 @@ If you specify `@path` on the command-line, then it will open `path` and read
|
||||||
command line options from it. These options are one per line; a blank line indicates
|
command line options from it. These options are one per line; a blank line indicates
|
||||||
an empty option. The file can use Unix or Windows style line endings, and must be
|
an empty option. The file can use Unix or Windows style line endings, and must be
|
||||||
encoded as UTF-8.
|
encoded as UTF-8.
|
||||||
|
|
||||||
|
## `--passes`: add more rustdoc passes
|
||||||
|
|
||||||
|
This flag is **deprecated**.
|
||||||
|
For more details on passes, see [the chapter on them](passes.md).
|
||||||
|
|
||||||
|
## `--no-defaults`: don't run default passes
|
||||||
|
|
||||||
|
This flag is **deprecated**.
|
||||||
|
For more details on passes, see [the chapter on them](passes.md).
|
||||||
|
|
||||||
|
## `-r`/`--input-format`: input format
|
||||||
|
|
||||||
|
This flag is **deprecated** and **has no effect**.
|
||||||
|
|
||||||
|
Rustdoc only supports Rust source code and Markdown input formats. If the
|
||||||
|
file ends in `.md` or `.markdown`, `rustdoc` treats it as a Markdown file.
|
||||||
|
Otherwise, it assumes that the input file is Rust.
|
||||||
|
|
|
@ -3,86 +3,9 @@
|
||||||
Rustdoc has a concept called "passes". These are transformations that
|
Rustdoc has a concept called "passes". These are transformations that
|
||||||
`rustdoc` runs on your documentation before producing its final output.
|
`rustdoc` runs on your documentation before producing its final output.
|
||||||
|
|
||||||
In addition to the passes below, check out the docs for these flags:
|
Customizing passes is **deprecated**. The available passes are not considered stable and may
|
||||||
|
change in any release.
|
||||||
|
|
||||||
* [`--passes`](command-line-arguments.md#--passes-add-more-rustdoc-passes)
|
In the past the most common use case for customizing passes was to omit the `strip-private` pass.
|
||||||
* [`--no-defaults`](command-line-arguments.md#--no-defaults-dont-run-default-passes)
|
You can do this more easily, and without risk of the pass being changed, by passing
|
||||||
|
[`--document-private-items`](./unstable-features.md#--document-private-items).
|
||||||
## Default passes
|
|
||||||
|
|
||||||
By default, rustdoc will run some passes, namely:
|
|
||||||
|
|
||||||
* `strip-hidden`
|
|
||||||
* `strip-private`
|
|
||||||
* `collapse-docs`
|
|
||||||
* `unindent-comments`
|
|
||||||
|
|
||||||
However, `strip-private` implies `strip-priv-imports`, and so effectively,
|
|
||||||
all passes are run by default.
|
|
||||||
|
|
||||||
## `strip-hidden`
|
|
||||||
|
|
||||||
This pass implements the `#[doc(hidden)]` attribute. When this pass runs, it
|
|
||||||
checks each item, and if it is annotated with this attribute, it removes it
|
|
||||||
from `rustdoc`'s output.
|
|
||||||
|
|
||||||
Without this pass, these items will remain in the output.
|
|
||||||
|
|
||||||
## `unindent-comments`
|
|
||||||
|
|
||||||
When you write a doc comment like this:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
/// This is a documentation comment.
|
|
||||||
# fn f() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
There's a space between the `///` and that `T`. That spacing isn't intended
|
|
||||||
to be a part of the output; it's there for humans, to help separate the doc
|
|
||||||
comment syntax from the text of the comment. This pass is what removes that
|
|
||||||
space.
|
|
||||||
|
|
||||||
The exact rules are left under-specified so that we can fix issues that we find.
|
|
||||||
|
|
||||||
Without this pass, the exact number of spaces is preserved.
|
|
||||||
|
|
||||||
## `collapse-docs`
|
|
||||||
|
|
||||||
With this pass, multiple `#[doc]` attributes are converted into one single
|
|
||||||
documentation string.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
#[doc = "This is the first line."]
|
|
||||||
#[doc = "This is the second line."]
|
|
||||||
# fn f() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Gets collapsed into a single doc string of
|
|
||||||
|
|
||||||
```text
|
|
||||||
This is the first line.
|
|
||||||
This is the second line.
|
|
||||||
```
|
|
||||||
|
|
||||||
## `strip-private`
|
|
||||||
|
|
||||||
This removes documentation for any non-public items, so for example:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
/// These are private docs.
|
|
||||||
struct Private;
|
|
||||||
|
|
||||||
/// These are public docs.
|
|
||||||
pub struct Public;
|
|
||||||
```
|
|
||||||
|
|
||||||
This pass removes the docs for `Private`, since they're not public.
|
|
||||||
|
|
||||||
This pass implies `strip-priv-imports`.
|
|
||||||
|
|
||||||
## `strip-priv-imports`
|
|
||||||
|
|
||||||
This is the same as `strip-private`, but for `extern crate` and `use`
|
|
||||||
statements instead of items.
|
|
||||||
|
|
|
@ -340,6 +340,30 @@ Some methodology notes about what rustdoc counts in this metric:
|
||||||
Public items that are not documented can be seen with the built-in `missing_docs` lint. Private
|
Public items that are not documented can be seen with the built-in `missing_docs` lint. Private
|
||||||
items that are not documented can be seen with Clippy's `missing_docs_in_private_items` lint.
|
items that are not documented can be seen with Clippy's `missing_docs_in_private_items` lint.
|
||||||
|
|
||||||
|
## `-w`/`--output-format`: output format
|
||||||
|
|
||||||
|
When using
|
||||||
|
[`--show-coverage`](https://doc.rust-lang.org/nightly/rustdoc/unstable-features.html#--show-coverage-get-statistics-about-code-documentation-coverage),
|
||||||
|
passing `--output-format json` will display the coverage information in JSON format. For example,
|
||||||
|
here is the JSON for a file with one documented item and one undocumented item:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// This item has documentation
|
||||||
|
pub fn foo() {}
|
||||||
|
|
||||||
|
pub fn no_documentation() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"no_std.rs":{"total":3,"with_docs":1,"total_examples":3,"with_examples":0}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the third item is the crate root, which in this case is undocumented.
|
||||||
|
|
||||||
|
When not using `--show-coverage`, `--output-format json` emits documentation in the experimental
|
||||||
|
[JSON format](https://github.com/rust-lang/rfcs/pull/2963). `--output-format html` has no effect,
|
||||||
|
and is also accepted on stable toolchains.
|
||||||
|
|
||||||
### `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests
|
### `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests
|
||||||
|
|
||||||
Using this flag looks like this:
|
Using this flag looks like this:
|
||||||
|
|
617
src/librustdoc/html/render/context.rs
Normal file
617
src/librustdoc/html/render/context.rs
Normal file
|
@ -0,0 +1,617 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::mpsc::{channel, Receiver};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
|
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
|
||||||
|
use rustc_middle::ty::TyCtxt;
|
||||||
|
use rustc_session::Session;
|
||||||
|
use rustc_span::edition::Edition;
|
||||||
|
use rustc_span::source_map::FileName;
|
||||||
|
use rustc_span::symbol::sym;
|
||||||
|
|
||||||
|
use super::cache::{build_index, ExternalLocation};
|
||||||
|
use super::print_item::{full_path, item_path, print_item};
|
||||||
|
use super::write_shared::write_shared;
|
||||||
|
use super::{
|
||||||
|
print_sidebar, settings, AllTypes, NameDoc, SharedContext, StylePath, BASIC_KEYWORDS,
|
||||||
|
CURRENT_DEPTH, INITIAL_IDS,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::clean::{self, AttributesExt};
|
||||||
|
use crate::config::RenderOptions;
|
||||||
|
use crate::docfs::{DocFS, PathError};
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::formats::cache::Cache;
|
||||||
|
use crate::formats::item_type::ItemType;
|
||||||
|
use crate::formats::FormatRenderer;
|
||||||
|
use crate::html::escape::Escape;
|
||||||
|
use crate::html::format::Buffer;
|
||||||
|
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
|
||||||
|
use crate::html::{layout, sources};
|
||||||
|
|
||||||
|
/// Major driving force in all rustdoc rendering. This contains information
|
||||||
|
/// about where in the tree-like hierarchy rendering is occurring and controls
|
||||||
|
/// how the current page is being rendered.
|
||||||
|
///
|
||||||
|
/// It is intended that this context is a lightweight object which can be fairly
|
||||||
|
/// easily cloned because it is cloned per work-job (about once per item in the
|
||||||
|
/// rustdoc tree).
|
||||||
|
#[derive(Clone)]
|
||||||
|
crate struct Context<'tcx> {
|
||||||
|
/// Current hierarchy of components leading down to what's currently being
|
||||||
|
/// rendered
|
||||||
|
crate current: Vec<String>,
|
||||||
|
/// The current destination folder of where HTML artifacts should be placed.
|
||||||
|
/// This changes as the context descends into the module hierarchy.
|
||||||
|
crate dst: PathBuf,
|
||||||
|
/// A flag, which when `true`, will render pages which redirect to the
|
||||||
|
/// real location of an item. This is used to allow external links to
|
||||||
|
/// publicly reused items to redirect to the right location.
|
||||||
|
crate render_redirect_pages: bool,
|
||||||
|
/// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set
|
||||||
|
/// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of
|
||||||
|
/// the crate.
|
||||||
|
crate redirections: Option<Rc<RefCell<FxHashMap<String, String>>>>,
|
||||||
|
/// The map used to ensure all generated 'id=' attributes are unique.
|
||||||
|
pub(super) id_map: Rc<RefCell<IdMap>>,
|
||||||
|
/// Tracks section IDs for `Deref` targets so they match in both the main
|
||||||
|
/// body and the sidebar.
|
||||||
|
pub(super) deref_id_map: Rc<RefCell<FxHashMap<DefId, String>>>,
|
||||||
|
crate shared: Arc<SharedContext<'tcx>>,
|
||||||
|
all: Rc<RefCell<AllTypes>>,
|
||||||
|
/// Storage for the errors produced while generating documentation so they
|
||||||
|
/// can be printed together at the end.
|
||||||
|
crate errors: Rc<Receiver<String>>,
|
||||||
|
crate cache: Rc<Cache>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tcx> Context<'tcx> {
|
||||||
|
pub(super) fn path(&self, filename: &str) -> PathBuf {
|
||||||
|
// We use splitn vs Path::extension here because we might get a filename
|
||||||
|
// like `style.min.css` and we want to process that into
|
||||||
|
// `style-suffix.min.css`. Path::extension would just return `css`
|
||||||
|
// which would result in `style.min-suffix.css` which isn't what we
|
||||||
|
// want.
|
||||||
|
let (base, ext) = filename.split_once('.').unwrap();
|
||||||
|
let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext);
|
||||||
|
self.dst.join(&filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn tcx(&self) -> TyCtxt<'tcx> {
|
||||||
|
self.shared.tcx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sess(&self) -> &'tcx Session {
|
||||||
|
&self.shared.tcx.sess
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn derive_id(&self, id: String) -> String {
|
||||||
|
let mut map = self.id_map.borrow_mut();
|
||||||
|
map.derive(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// String representation of how to get back to the root path of the 'doc/'
|
||||||
|
/// folder in terms of a relative URL.
|
||||||
|
pub(super) fn root_path(&self) -> String {
|
||||||
|
"../".repeat(self.current.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_item(&self, it: &clean::Item, pushname: bool) -> String {
|
||||||
|
// A little unfortunate that this is done like this, but it sure
|
||||||
|
// does make formatting *a lot* nicer.
|
||||||
|
CURRENT_DEPTH.with(|slot| {
|
||||||
|
slot.set(self.current.len());
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut title = if it.is_primitive() || it.is_keyword() {
|
||||||
|
// No need to include the namespace for primitive types and keywords
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
self.current.join("::")
|
||||||
|
};
|
||||||
|
if pushname {
|
||||||
|
if !title.is_empty() {
|
||||||
|
title.push_str("::");
|
||||||
|
}
|
||||||
|
title.push_str(&it.name.unwrap().as_str());
|
||||||
|
}
|
||||||
|
title.push_str(" - Rust");
|
||||||
|
let tyname = it.type_();
|
||||||
|
let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc));
|
||||||
|
let desc = if let Some(desc) = desc {
|
||||||
|
desc
|
||||||
|
} else if it.is_crate() {
|
||||||
|
format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"API documentation for the Rust `{}` {} in crate `{}`.",
|
||||||
|
it.name.as_ref().unwrap(),
|
||||||
|
tyname,
|
||||||
|
self.shared.layout.krate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let keywords = make_item_keywords(it);
|
||||||
|
let page = layout::Page {
|
||||||
|
css_class: tyname.as_str(),
|
||||||
|
root_path: &self.root_path(),
|
||||||
|
static_root_path: self.shared.static_root_path.as_deref(),
|
||||||
|
title: &title,
|
||||||
|
description: &desc,
|
||||||
|
keywords: &keywords,
|
||||||
|
resource_suffix: &self.shared.resource_suffix,
|
||||||
|
extra_scripts: &[],
|
||||||
|
static_extra_scripts: &[],
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
self.id_map.borrow_mut().reset();
|
||||||
|
self.id_map.borrow_mut().populate(&INITIAL_IDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.render_redirect_pages {
|
||||||
|
layout::render(
|
||||||
|
&self.shared.layout,
|
||||||
|
&page,
|
||||||
|
|buf: &mut _| print_sidebar(self, it, buf),
|
||||||
|
|buf: &mut _| print_item(self, it, buf),
|
||||||
|
&self.shared.style_files,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) {
|
||||||
|
let mut path = String::new();
|
||||||
|
for name in &names[..names.len() - 1] {
|
||||||
|
path.push_str(name);
|
||||||
|
path.push('/');
|
||||||
|
}
|
||||||
|
path.push_str(&item_path(ty, names.last().unwrap()));
|
||||||
|
match self.redirections {
|
||||||
|
Some(ref redirections) => {
|
||||||
|
let mut current_path = String::new();
|
||||||
|
for name in &self.current {
|
||||||
|
current_path.push_str(name);
|
||||||
|
current_path.push('/');
|
||||||
|
}
|
||||||
|
current_path.push_str(&item_path(ty, names.last().unwrap()));
|
||||||
|
redirections.borrow_mut().insert(current_path, path);
|
||||||
|
}
|
||||||
|
None => return layout::redirect(&format!("{}{}", self.root_path(), path)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a map of items shown in the sidebar to a plain-text summary of their docs.
|
||||||
|
fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> {
|
||||||
|
// BTreeMap instead of HashMap to get a sorted output
|
||||||
|
let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new();
|
||||||
|
for item in &m.items {
|
||||||
|
if item.is_stripped() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let short = item.type_();
|
||||||
|
let myname = match item.name {
|
||||||
|
None => continue,
|
||||||
|
Some(ref s) => s.to_string(),
|
||||||
|
};
|
||||||
|
let short = short.to_string();
|
||||||
|
map.entry(short).or_default().push((
|
||||||
|
myname,
|
||||||
|
Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.shared.sort_modules_alphabetically {
|
||||||
|
for items in map.values_mut() {
|
||||||
|
items.sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a url appropriate for an `href` attribute back to the source of
|
||||||
|
/// this item.
|
||||||
|
///
|
||||||
|
/// The url generated, when clicked, will redirect the browser back to the
|
||||||
|
/// original source code.
|
||||||
|
///
|
||||||
|
/// If `None` is returned, then a source link couldn't be generated. This
|
||||||
|
/// may happen, for example, with externally inlined items where the source
|
||||||
|
/// of their crate documentation isn't known.
|
||||||
|
pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> {
|
||||||
|
if item.source.is_dummy() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut root = self.root_path();
|
||||||
|
let mut path = String::new();
|
||||||
|
let cnum = item.source.cnum(self.sess());
|
||||||
|
|
||||||
|
// We can safely ignore synthetic `SourceFile`s.
|
||||||
|
let file = match item.source.filename(self.sess()) {
|
||||||
|
FileName::Real(ref path) => path.local_path().to_path_buf(),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let file = &file;
|
||||||
|
|
||||||
|
let symbol;
|
||||||
|
let (krate, path) = if cnum == LOCAL_CRATE {
|
||||||
|
if let Some(path) = self.shared.local_sources.get(file) {
|
||||||
|
(self.shared.layout.krate.as_str(), path)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let (krate, src_root) = match *self.cache.extern_locations.get(&cnum)? {
|
||||||
|
(name, ref src, ExternalLocation::Local) => (name, src),
|
||||||
|
(name, ref src, ExternalLocation::Remote(ref s)) => {
|
||||||
|
root = s.to_string();
|
||||||
|
(name, src)
|
||||||
|
}
|
||||||
|
(_, _, ExternalLocation::Unknown) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
sources::clean_path(&src_root, file, false, |component| {
|
||||||
|
path.push_str(&component.to_string_lossy());
|
||||||
|
path.push('/');
|
||||||
|
});
|
||||||
|
let mut fname = file.file_name().expect("source has no filename").to_os_string();
|
||||||
|
fname.push(".html");
|
||||||
|
path.push_str(&fname.to_string_lossy());
|
||||||
|
symbol = krate.as_str();
|
||||||
|
(&*symbol, &path)
|
||||||
|
};
|
||||||
|
|
||||||
|
let loline = item.source.lo(self.sess()).line;
|
||||||
|
let hiline = item.source.hi(self.sess()).line;
|
||||||
|
let lines =
|
||||||
|
if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) };
|
||||||
|
Some(format!(
|
||||||
|
"{root}src/{krate}/{path}#{lines}",
|
||||||
|
root = Escape(&root),
|
||||||
|
krate = krate,
|
||||||
|
path = path,
|
||||||
|
lines = lines
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the documentation for `crate` into the directory `dst`
|
||||||
|
impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
|
||||||
|
fn descr() -> &'static str {
|
||||||
|
"html"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
mut krate: clean::Crate,
|
||||||
|
options: RenderOptions,
|
||||||
|
edition: Edition,
|
||||||
|
mut cache: Cache,
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
|
) -> Result<(Self, clean::Crate), Error> {
|
||||||
|
// need to save a copy of the options for rendering the index page
|
||||||
|
let md_opts = options.clone();
|
||||||
|
let RenderOptions {
|
||||||
|
output,
|
||||||
|
external_html,
|
||||||
|
id_map,
|
||||||
|
playground_url,
|
||||||
|
sort_modules_alphabetically,
|
||||||
|
themes: style_files,
|
||||||
|
default_settings,
|
||||||
|
extension_css,
|
||||||
|
resource_suffix,
|
||||||
|
static_root_path,
|
||||||
|
generate_search_filter,
|
||||||
|
unstable_features,
|
||||||
|
generate_redirect_map,
|
||||||
|
..
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let src_root = match krate.src {
|
||||||
|
FileName::Real(ref p) => match p.local_path().parent() {
|
||||||
|
Some(p) => p.to_path_buf(),
|
||||||
|
None => PathBuf::new(),
|
||||||
|
},
|
||||||
|
_ => PathBuf::new(),
|
||||||
|
};
|
||||||
|
// If user passed in `--playground-url` arg, we fill in crate name here
|
||||||
|
let mut playground = None;
|
||||||
|
if let Some(url) = playground_url {
|
||||||
|
playground =
|
||||||
|
Some(markdown::Playground { crate_name: Some(krate.name.to_string()), url });
|
||||||
|
}
|
||||||
|
let mut layout = layout::Layout {
|
||||||
|
logo: String::new(),
|
||||||
|
favicon: String::new(),
|
||||||
|
external_html,
|
||||||
|
default_settings,
|
||||||
|
krate: krate.name.to_string(),
|
||||||
|
css_file_extension: extension_css,
|
||||||
|
generate_search_filter,
|
||||||
|
};
|
||||||
|
let mut issue_tracker_base_url = None;
|
||||||
|
let mut include_sources = true;
|
||||||
|
|
||||||
|
// Crawl the crate attributes looking for attributes which control how we're
|
||||||
|
// going to emit HTML
|
||||||
|
if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) {
|
||||||
|
for attr in attrs.lists(sym::doc) {
|
||||||
|
match (attr.name_or_empty(), attr.value_str()) {
|
||||||
|
(sym::html_favicon_url, Some(s)) => {
|
||||||
|
layout.favicon = s.to_string();
|
||||||
|
}
|
||||||
|
(sym::html_logo_url, Some(s)) => {
|
||||||
|
layout.logo = s.to_string();
|
||||||
|
}
|
||||||
|
(sym::html_playground_url, Some(s)) => {
|
||||||
|
playground = Some(markdown::Playground {
|
||||||
|
crate_name: Some(krate.name.to_string()),
|
||||||
|
url: s.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
(sym::issue_tracker_base_url, Some(s)) => {
|
||||||
|
issue_tracker_base_url = Some(s.to_string());
|
||||||
|
}
|
||||||
|
(sym::html_no_source, None) if attr.is_word() => {
|
||||||
|
include_sources = false;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (sender, receiver) = channel();
|
||||||
|
let mut scx = SharedContext {
|
||||||
|
tcx,
|
||||||
|
collapsed: krate.collapsed,
|
||||||
|
src_root,
|
||||||
|
include_sources,
|
||||||
|
local_sources: Default::default(),
|
||||||
|
issue_tracker_base_url,
|
||||||
|
layout,
|
||||||
|
created_dirs: Default::default(),
|
||||||
|
sort_modules_alphabetically,
|
||||||
|
style_files,
|
||||||
|
resource_suffix,
|
||||||
|
static_root_path,
|
||||||
|
fs: DocFS::new(sender),
|
||||||
|
edition,
|
||||||
|
codes: ErrorCodes::from(unstable_features.is_nightly_build()),
|
||||||
|
playground,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the default themes to the `Vec` of stylepaths
|
||||||
|
//
|
||||||
|
// Note that these must be added before `sources::render` is called
|
||||||
|
// so that the resulting source pages are styled
|
||||||
|
//
|
||||||
|
// `light.css` is not disabled because it is the stylesheet that stays loaded
|
||||||
|
// by the browser as the theme stylesheet. The theme system (hackily) works by
|
||||||
|
// changing the href to this stylesheet. All other themes are disabled to
|
||||||
|
// prevent rule conflicts
|
||||||
|
scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false });
|
||||||
|
scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true });
|
||||||
|
scx.style_files.push(StylePath { path: PathBuf::from("ayu.css"), disabled: true });
|
||||||
|
|
||||||
|
let dst = output;
|
||||||
|
scx.ensure_dir(&dst)?;
|
||||||
|
krate = sources::render(&dst, &mut scx, krate)?;
|
||||||
|
|
||||||
|
// Build our search index
|
||||||
|
let index = build_index(&krate, &mut cache, tcx);
|
||||||
|
|
||||||
|
let mut cx = Context {
|
||||||
|
current: Vec::new(),
|
||||||
|
dst,
|
||||||
|
render_redirect_pages: false,
|
||||||
|
id_map: Rc::new(RefCell::new(id_map)),
|
||||||
|
deref_id_map: Rc::new(RefCell::new(FxHashMap::default())),
|
||||||
|
shared: Arc::new(scx),
|
||||||
|
all: Rc::new(RefCell::new(AllTypes::new())),
|
||||||
|
errors: Rc::new(receiver),
|
||||||
|
cache: Rc::new(cache),
|
||||||
|
redirections: if generate_redirect_map { Some(Default::default()) } else { None },
|
||||||
|
};
|
||||||
|
|
||||||
|
CURRENT_DEPTH.with(|s| s.set(0));
|
||||||
|
|
||||||
|
// Write shared runs within a flock; disable thread dispatching of IO temporarily.
|
||||||
|
Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
|
||||||
|
write_shared(&cx, &krate, index, &md_opts)?;
|
||||||
|
Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
|
||||||
|
Ok((cx, krate))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_krate(
|
||||||
|
&mut self,
|
||||||
|
krate: &clean::Crate,
|
||||||
|
diag: &rustc_errors::Handler,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let final_file = self.dst.join(&*krate.name.as_str()).join("all.html");
|
||||||
|
let settings_file = self.dst.join("settings.html");
|
||||||
|
let crate_name = krate.name;
|
||||||
|
|
||||||
|
let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
|
||||||
|
if !root_path.ends_with('/') {
|
||||||
|
root_path.push('/');
|
||||||
|
}
|
||||||
|
let mut page = layout::Page {
|
||||||
|
title: "List of all items in this crate",
|
||||||
|
css_class: "mod",
|
||||||
|
root_path: "../",
|
||||||
|
static_root_path: self.shared.static_root_path.as_deref(),
|
||||||
|
description: "List of all items in this crate",
|
||||||
|
keywords: BASIC_KEYWORDS,
|
||||||
|
resource_suffix: &self.shared.resource_suffix,
|
||||||
|
extra_scripts: &[],
|
||||||
|
static_extra_scripts: &[],
|
||||||
|
};
|
||||||
|
let sidebar = if let Some(ref version) = self.cache.crate_version {
|
||||||
|
format!(
|
||||||
|
"<p class=\"location\">Crate {}</p>\
|
||||||
|
<div class=\"block version\">\
|
||||||
|
<p>Version {}</p>\
|
||||||
|
</div>\
|
||||||
|
<a id=\"all-types\" href=\"index.html\"><p>Back to index</p></a>",
|
||||||
|
crate_name,
|
||||||
|
Escape(version),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
let all = self.all.replace(AllTypes::new());
|
||||||
|
let v = layout::render(
|
||||||
|
&self.shared.layout,
|
||||||
|
&page,
|
||||||
|
sidebar,
|
||||||
|
|buf: &mut Buffer| all.print(buf),
|
||||||
|
&self.shared.style_files,
|
||||||
|
);
|
||||||
|
self.shared.fs.write(&final_file, v.as_bytes())?;
|
||||||
|
|
||||||
|
// Generating settings page.
|
||||||
|
page.title = "Rustdoc settings";
|
||||||
|
page.description = "Settings of Rustdoc";
|
||||||
|
page.root_path = "./";
|
||||||
|
|
||||||
|
let mut style_files = self.shared.style_files.clone();
|
||||||
|
let sidebar = "<p class=\"location\">Settings</p><div class=\"sidebar-elems\"></div>";
|
||||||
|
style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false });
|
||||||
|
let v = layout::render(
|
||||||
|
&self.shared.layout,
|
||||||
|
&page,
|
||||||
|
sidebar,
|
||||||
|
settings(
|
||||||
|
self.shared.static_root_path.as_deref().unwrap_or("./"),
|
||||||
|
&self.shared.resource_suffix,
|
||||||
|
&self.shared.style_files,
|
||||||
|
)?,
|
||||||
|
&style_files,
|
||||||
|
);
|
||||||
|
self.shared.fs.write(&settings_file, v.as_bytes())?;
|
||||||
|
if let Some(redirections) = self.redirections.take() {
|
||||||
|
if !redirections.borrow().is_empty() {
|
||||||
|
let redirect_map_path =
|
||||||
|
self.dst.join(&*krate.name.as_str()).join("redirect-map.json");
|
||||||
|
let paths = serde_json::to_string(&*redirections.borrow()).unwrap();
|
||||||
|
self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?;
|
||||||
|
self.shared.fs.write(&redirect_map_path, paths.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush pending errors.
|
||||||
|
Arc::get_mut(&mut self.shared).unwrap().fs.close();
|
||||||
|
let nb_errors = self.errors.iter().map(|err| diag.struct_err(&err).emit()).count();
|
||||||
|
if nb_errors > 0 {
|
||||||
|
Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), ""))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mod_item_in(&mut self, item: &clean::Item, item_name: &str) -> Result<(), Error> {
|
||||||
|
// Stripped modules survive the rustdoc passes (i.e., `strip-private`)
|
||||||
|
// if they contain impls for public types. These modules can also
|
||||||
|
// contain items such as publicly re-exported structures.
|
||||||
|
//
|
||||||
|
// External crates will provide links to these structures, so
|
||||||
|
// these modules are recursed into, but not rendered normally
|
||||||
|
// (a flag on the context).
|
||||||
|
if !self.render_redirect_pages {
|
||||||
|
self.render_redirect_pages = item.is_stripped();
|
||||||
|
}
|
||||||
|
let scx = &self.shared;
|
||||||
|
self.dst.push(item_name);
|
||||||
|
self.current.push(item_name.to_owned());
|
||||||
|
|
||||||
|
info!("Recursing into {}", self.dst.display());
|
||||||
|
|
||||||
|
let buf = self.render_item(item, false);
|
||||||
|
// 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");
|
||||||
|
scx.fs.write(&joint_dst, buf.as_bytes())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render sidebar-items.js used throughout this module.
|
||||||
|
if !self.render_redirect_pages {
|
||||||
|
let module = match *item.kind {
|
||||||
|
clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let items = self.build_sidebar_items(module);
|
||||||
|
let js_dst = self.dst.join("sidebar-items.js");
|
||||||
|
let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap());
|
||||||
|
scx.fs.write(&js_dst, &v)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> {
|
||||||
|
info!("Recursed; leaving {}", self.dst.display());
|
||||||
|
|
||||||
|
// Go back to where we were at
|
||||||
|
self.dst.pop();
|
||||||
|
self.current.pop();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item(&mut self, item: clean::Item) -> Result<(), Error> {
|
||||||
|
// Stripped modules survive the rustdoc passes (i.e., `strip-private`)
|
||||||
|
// if they contain impls for public types. These modules can also
|
||||||
|
// contain items such as publicly re-exported structures.
|
||||||
|
//
|
||||||
|
// External crates will provide links to these structures, so
|
||||||
|
// these modules are recursed into, but not rendered normally
|
||||||
|
// (a flag on the context).
|
||||||
|
if !self.render_redirect_pages {
|
||||||
|
self.render_redirect_pages = item.is_stripped();
|
||||||
|
}
|
||||||
|
|
||||||
|
let buf = self.render_item(&item, true);
|
||||||
|
// buf will be empty if the item is stripped and there is no redirect for it
|
||||||
|
if !buf.is_empty() {
|
||||||
|
let name = item.name.as_ref().unwrap();
|
||||||
|
let item_type = item.type_();
|
||||||
|
let file_name = &item_path(item_type, &name.as_str());
|
||||||
|
self.shared.ensure_dir(&self.dst)?;
|
||||||
|
let joint_dst = self.dst.join(file_name);
|
||||||
|
self.shared.fs.write(&joint_dst, buf.as_bytes())?;
|
||||||
|
|
||||||
|
if !self.render_redirect_pages {
|
||||||
|
self.all.borrow_mut().append(full_path(self, &item), &item_type);
|
||||||
|
}
|
||||||
|
// If the item is a macro, redirect from the old macro URL (with !)
|
||||||
|
// to the new one (without).
|
||||||
|
if item_type == ItemType::Macro {
|
||||||
|
let redir_name = format!("{}.{}!.html", item_type, name);
|
||||||
|
if let Some(ref redirections) = self.redirections {
|
||||||
|
let crate_name = &self.shared.layout.krate;
|
||||||
|
redirections.borrow_mut().insert(
|
||||||
|
format!("{}/{}", crate_name, redir_name),
|
||||||
|
format!("{}/{}", crate_name, file_name),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let v = layout::redirect(file_name);
|
||||||
|
let redir_dst = self.dst.join(redir_name);
|
||||||
|
self.shared.fs.write(&redir_dst, v.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache(&self) -> &Cache {
|
||||||
|
&self.cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_item_keywords(it: &clean::Item) -> String {
|
||||||
|
format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap())
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
1420
src/librustdoc/html/render/print_item.rs
Normal file
1420
src/librustdoc/html/render/print_item.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,7 @@
|
||||||
use super::*;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use super::print_item::compare_names;
|
||||||
|
use super::{AllTypes, Buffer};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compare_names() {
|
fn test_compare_names() {
|
||||||
|
|
542
src/librustdoc/html/render/write_shared.rs
Normal file
542
src/librustdoc/html/render/write_shared.rs
Normal file
|
@ -0,0 +1,542 @@
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::io::{self, BufReader};
|
||||||
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use rustc_data_structures::flock;
|
||||||
|
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::{collect_paths_for_type, ensure_trailing_slash, Context, BASIC_KEYWORDS};
|
||||||
|
use crate::clean::Crate;
|
||||||
|
use crate::config::RenderOptions;
|
||||||
|
use crate::docfs::{DocFS, PathError};
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::formats::FormatRenderer;
|
||||||
|
use crate::html::{layout, static_files};
|
||||||
|
|
||||||
|
pub(super) fn write_shared(
|
||||||
|
cx: &Context<'_>,
|
||||||
|
krate: &Crate,
|
||||||
|
search_index: String,
|
||||||
|
options: &RenderOptions,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Write out the shared files. Note that these are shared among all rustdoc
|
||||||
|
// docs placed in the output directory, so this needs to be a synchronized
|
||||||
|
// operation with respect to all other rustdocs running around.
|
||||||
|
let lock_file = cx.dst.join(".lock");
|
||||||
|
let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
|
||||||
|
|
||||||
|
// Add all the static files. These may already exist, but we just
|
||||||
|
// overwrite them anyway to make sure that they're fresh and up-to-date.
|
||||||
|
|
||||||
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("rustdoc.css"),
|
||||||
|
static_files::RUSTDOC_CSS,
|
||||||
|
options.enable_minification,
|
||||||
|
)?;
|
||||||
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("settings.css"),
|
||||||
|
static_files::SETTINGS_CSS,
|
||||||
|
options.enable_minification,
|
||||||
|
)?;
|
||||||
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("noscript.css"),
|
||||||
|
static_files::NOSCRIPT_CSS,
|
||||||
|
options.enable_minification,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// To avoid "light.css" to be overwritten, we'll first run over the received themes and only
|
||||||
|
// then we'll run over the "official" styles.
|
||||||
|
let mut themes: FxHashSet<String> = FxHashSet::default();
|
||||||
|
|
||||||
|
for entry in &cx.shared.style_files {
|
||||||
|
let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path);
|
||||||
|
let extension =
|
||||||
|
try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
|
||||||
|
|
||||||
|
// Handle the official themes
|
||||||
|
match theme {
|
||||||
|
"light" => write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("light.css"),
|
||||||
|
static_files::themes::LIGHT,
|
||||||
|
options.enable_minification,
|
||||||
|
)?,
|
||||||
|
"dark" => write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("dark.css"),
|
||||||
|
static_files::themes::DARK,
|
||||||
|
options.enable_minification,
|
||||||
|
)?,
|
||||||
|
"ayu" => write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("ayu.css"),
|
||||||
|
static_files::themes::AYU,
|
||||||
|
options.enable_minification,
|
||||||
|
)?,
|
||||||
|
_ => {
|
||||||
|
// Handle added third-party themes
|
||||||
|
let content = try_err!(fs::read(&entry.path), &entry.path);
|
||||||
|
cx.shared
|
||||||
|
.fs
|
||||||
|
.write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
themes.insert(theme.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let write = |p, c| cx.shared.fs.write(p, c);
|
||||||
|
if (*cx.shared).layout.logo.is_empty() {
|
||||||
|
write(cx.path("rust-logo.png"), static_files::RUST_LOGO)?;
|
||||||
|
}
|
||||||
|
if (*cx.shared).layout.favicon.is_empty() {
|
||||||
|
write(cx.path("favicon.svg"), static_files::RUST_FAVICON_SVG)?;
|
||||||
|
write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?;
|
||||||
|
write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?;
|
||||||
|
}
|
||||||
|
write(cx.path("brush.svg"), static_files::BRUSH_SVG)?;
|
||||||
|
write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?;
|
||||||
|
write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?;
|
||||||
|
|
||||||
|
let mut themes: Vec<&String> = themes.iter().collect();
|
||||||
|
themes.sort();
|
||||||
|
// To avoid theme switch latencies as much as possible, we put everything theme related
|
||||||
|
// at the beginning of the html files into another js file.
|
||||||
|
let theme_js = format!(
|
||||||
|
r#"var themes = document.getElementById("theme-choices");
|
||||||
|
var themePicker = document.getElementById("theme-picker");
|
||||||
|
|
||||||
|
function showThemeButtonState() {{
|
||||||
|
themes.style.display = "block";
|
||||||
|
themePicker.style.borderBottomRightRadius = "0";
|
||||||
|
themePicker.style.borderBottomLeftRadius = "0";
|
||||||
|
}}
|
||||||
|
|
||||||
|
function hideThemeButtonState() {{
|
||||||
|
themes.style.display = "none";
|
||||||
|
themePicker.style.borderBottomRightRadius = "3px";
|
||||||
|
themePicker.style.borderBottomLeftRadius = "3px";
|
||||||
|
}}
|
||||||
|
|
||||||
|
function switchThemeButtonState() {{
|
||||||
|
if (themes.style.display === "block") {{
|
||||||
|
hideThemeButtonState();
|
||||||
|
}} else {{
|
||||||
|
showThemeButtonState();
|
||||||
|
}}
|
||||||
|
}};
|
||||||
|
|
||||||
|
function handleThemeButtonsBlur(e) {{
|
||||||
|
var active = document.activeElement;
|
||||||
|
var related = e.relatedTarget;
|
||||||
|
|
||||||
|
if (active.id !== "theme-picker" &&
|
||||||
|
(!active.parentNode || active.parentNode.id !== "theme-choices") &&
|
||||||
|
(!related ||
|
||||||
|
(related.id !== "theme-picker" &&
|
||||||
|
(!related.parentNode || related.parentNode.id !== "theme-choices")))) {{
|
||||||
|
hideThemeButtonState();
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
themePicker.onclick = switchThemeButtonState;
|
||||||
|
themePicker.onblur = handleThemeButtonsBlur;
|
||||||
|
{}.forEach(function(item) {{
|
||||||
|
var but = document.createElement("button");
|
||||||
|
but.textContent = item;
|
||||||
|
but.onclick = function(el) {{
|
||||||
|
switchTheme(currentTheme, mainTheme, item, true);
|
||||||
|
useSystemTheme(false);
|
||||||
|
}};
|
||||||
|
but.onblur = handleThemeButtonsBlur;
|
||||||
|
themes.appendChild(but);
|
||||||
|
}});"#,
|
||||||
|
serde_json::to_string(&themes).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
write_minify(&cx.shared.fs, cx.path("theme.js"), &theme_js, options.enable_minification)?;
|
||||||
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("main.js"),
|
||||||
|
static_files::MAIN_JS,
|
||||||
|
options.enable_minification,
|
||||||
|
)?;
|
||||||
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("settings.js"),
|
||||||
|
static_files::SETTINGS_JS,
|
||||||
|
options.enable_minification,
|
||||||
|
)?;
|
||||||
|
if cx.shared.include_sources {
|
||||||
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("source-script.js"),
|
||||||
|
static_files::sidebar::SOURCE_SCRIPT,
|
||||||
|
options.enable_minification,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("storage.js"),
|
||||||
|
&format!(
|
||||||
|
"var resourcesSuffix = \"{}\";{}",
|
||||||
|
cx.shared.resource_suffix,
|
||||||
|
static_files::STORAGE_JS
|
||||||
|
),
|
||||||
|
options.enable_minification,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref css) = cx.shared.layout.css_file_extension {
|
||||||
|
let out = cx.path("theme.css");
|
||||||
|
let buffer = try_err!(fs::read_to_string(css), css);
|
||||||
|
if !options.enable_minification {
|
||||||
|
cx.shared.fs.write(&out, &buffer)?;
|
||||||
|
} else {
|
||||||
|
write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.path("normalize.css"),
|
||||||
|
static_files::NORMALIZE_CSS,
|
||||||
|
options.enable_minification,
|
||||||
|
)?;
|
||||||
|
write(cx.dst.join("FiraSans-Regular.woff2"), static_files::fira_sans::REGULAR2)?;
|
||||||
|
write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?;
|
||||||
|
write(cx.dst.join("FiraSans-Regular.woff"), static_files::fira_sans::REGULAR)?;
|
||||||
|
write(cx.dst.join("FiraSans-Medium.woff"), static_files::fira_sans::MEDIUM)?;
|
||||||
|
write(cx.dst.join("FiraSans-LICENSE.txt"), static_files::fira_sans::LICENSE)?;
|
||||||
|
write(cx.dst.join("SourceSerifPro-Regular.ttf.woff"), static_files::source_serif_pro::REGULAR)?;
|
||||||
|
write(cx.dst.join("SourceSerifPro-Bold.ttf.woff"), static_files::source_serif_pro::BOLD)?;
|
||||||
|
write(cx.dst.join("SourceSerifPro-It.ttf.woff"), static_files::source_serif_pro::ITALIC)?;
|
||||||
|
write(cx.dst.join("SourceSerifPro-LICENSE.md"), static_files::source_serif_pro::LICENSE)?;
|
||||||
|
write(cx.dst.join("SourceCodePro-Regular.woff"), static_files::source_code_pro::REGULAR)?;
|
||||||
|
write(cx.dst.join("SourceCodePro-Semibold.woff"), static_files::source_code_pro::SEMIBOLD)?;
|
||||||
|
write(cx.dst.join("SourceCodePro-LICENSE.txt"), static_files::source_code_pro::LICENSE)?;
|
||||||
|
write(cx.dst.join("LICENSE-MIT.txt"), static_files::LICENSE_MIT)?;
|
||||||
|
write(cx.dst.join("LICENSE-APACHE.txt"), static_files::LICENSE_APACHE)?;
|
||||||
|
write(cx.dst.join("COPYRIGHT.txt"), static_files::COPYRIGHT)?;
|
||||||
|
|
||||||
|
fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec<String>, Vec<String>)> {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
let mut krates = Vec::new();
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
let prefix = format!(r#"{}["{}"]"#, key, krate);
|
||||||
|
for line in BufReader::new(File::open(path)?).lines() {
|
||||||
|
let line = line?;
|
||||||
|
if !line.starts_with(key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if line.starts_with(&prefix) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ret.push(line.to_string());
|
||||||
|
krates.push(
|
||||||
|
line[key.len() + 2..]
|
||||||
|
.split('"')
|
||||||
|
.next()
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.unwrap_or_else(String::new),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((ret, krates))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec<String>, Vec<String>)> {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
let mut krates = Vec::new();
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
let prefix = format!("\"{}\"", krate);
|
||||||
|
for line in BufReader::new(File::open(path)?).lines() {
|
||||||
|
let line = line?;
|
||||||
|
if !line.starts_with('"') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if line.starts_with(&prefix) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if line.ends_with(",\\") {
|
||||||
|
ret.push(line[..line.len() - 2].to_string());
|
||||||
|
} else {
|
||||||
|
// Ends with "\\" (it's the case for the last added crate line)
|
||||||
|
ret.push(line[..line.len() - 1].to_string());
|
||||||
|
}
|
||||||
|
krates.push(
|
||||||
|
line.split('"')
|
||||||
|
.find(|s| !s.is_empty())
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.unwrap_or_else(String::new),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((ret, krates))
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Hierarchy {
|
||||||
|
elem: OsString,
|
||||||
|
children: FxHashMap<OsString, Hierarchy>,
|
||||||
|
elems: FxHashSet<OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hierarchy {
|
||||||
|
fn new(elem: OsString) -> Hierarchy {
|
||||||
|
Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_json_string(&self) -> String {
|
||||||
|
let mut subs: Vec<&Hierarchy> = self.children.values().collect();
|
||||||
|
subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
|
||||||
|
let mut files = self
|
||||||
|
.elems
|
||||||
|
.iter()
|
||||||
|
.map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion")))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
files.sort_unstable();
|
||||||
|
let subs = subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().join(",");
|
||||||
|
let dirs =
|
||||||
|
if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs) };
|
||||||
|
let files = files.join(",");
|
||||||
|
let files =
|
||||||
|
if files.is_empty() { String::new() } else { format!(",\"files\":[{}]", files) };
|
||||||
|
format!(
|
||||||
|
"{{\"name\":\"{name}\"{dirs}{files}}}",
|
||||||
|
name = self.elem.to_str().expect("invalid osstring conversion"),
|
||||||
|
dirs = dirs,
|
||||||
|
files = files
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cx.shared.include_sources {
|
||||||
|
let mut hierarchy = Hierarchy::new(OsString::new());
|
||||||
|
for source in cx
|
||||||
|
.shared
|
||||||
|
.local_sources
|
||||||
|
.iter()
|
||||||
|
.filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
|
||||||
|
{
|
||||||
|
let mut h = &mut hierarchy;
|
||||||
|
let mut elems = source
|
||||||
|
.components()
|
||||||
|
.filter_map(|s| match s {
|
||||||
|
Component::Normal(s) => Some(s.to_owned()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.peekable();
|
||||||
|
loop {
|
||||||
|
let cur_elem = elems.next().expect("empty file path");
|
||||||
|
if elems.peek().is_none() {
|
||||||
|
h.elems.insert(cur_elem);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
let e = cur_elem.clone();
|
||||||
|
h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix));
|
||||||
|
let (mut all_sources, _krates) =
|
||||||
|
try_err!(collect(&dst, &krate.name.as_str(), "sourcesIndex"), &dst);
|
||||||
|
all_sources.push(format!(
|
||||||
|
"sourcesIndex[\"{}\"] = {};",
|
||||||
|
&krate.name,
|
||||||
|
hierarchy.to_json_string()
|
||||||
|
));
|
||||||
|
all_sources.sort();
|
||||||
|
let v = format!(
|
||||||
|
"var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();\n",
|
||||||
|
all_sources.join("\n")
|
||||||
|
);
|
||||||
|
cx.shared.fs.write(&dst, v.as_bytes())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the search index and crate list.
|
||||||
|
let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
|
||||||
|
let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
|
||||||
|
all_indexes.push(search_index);
|
||||||
|
krates.push(krate.name.to_string());
|
||||||
|
krates.sort();
|
||||||
|
|
||||||
|
// Sort the indexes by crate so the file will be generated identically even
|
||||||
|
// with rustdoc running in parallel.
|
||||||
|
all_indexes.sort();
|
||||||
|
{
|
||||||
|
let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
|
||||||
|
v.push_str(&all_indexes.join(",\\\n"));
|
||||||
|
v.push_str("\\\n}');\ninitSearch(searchIndex);");
|
||||||
|
cx.shared.fs.write(&dst, &v)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix));
|
||||||
|
let crate_list =
|
||||||
|
format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(","));
|
||||||
|
cx.shared.fs.write(&crate_list_dst, &crate_list)?;
|
||||||
|
|
||||||
|
if options.enable_index_page {
|
||||||
|
if let Some(index_page) = options.index_page.clone() {
|
||||||
|
let mut md_opts = options.clone();
|
||||||
|
md_opts.output = cx.dst.clone();
|
||||||
|
md_opts.external_html = (*cx.shared).layout.external_html.clone();
|
||||||
|
|
||||||
|
crate::markdown::render(&index_page, md_opts, cx.shared.edition)
|
||||||
|
.map_err(|e| Error::new(e, &index_page))?;
|
||||||
|
} else {
|
||||||
|
let dst = cx.dst.join("index.html");
|
||||||
|
let page = layout::Page {
|
||||||
|
title: "Index of crates",
|
||||||
|
css_class: "mod",
|
||||||
|
root_path: "./",
|
||||||
|
static_root_path: cx.shared.static_root_path.as_deref(),
|
||||||
|
description: "List of crates",
|
||||||
|
keywords: BASIC_KEYWORDS,
|
||||||
|
resource_suffix: &cx.shared.resource_suffix,
|
||||||
|
extra_scripts: &[],
|
||||||
|
static_extra_scripts: &[],
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = format!(
|
||||||
|
"<h1 class=\"fqn\">\
|
||||||
|
<span class=\"in-band\">List of all crates</span>\
|
||||||
|
</h1><ul class=\"crate mod\">{}</ul>",
|
||||||
|
krates
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
format!(
|
||||||
|
"<li><a class=\"crate mod\" href=\"{}index.html\">{}</a></li>",
|
||||||
|
ensure_trailing_slash(s),
|
||||||
|
s
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<String>()
|
||||||
|
);
|
||||||
|
let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files);
|
||||||
|
cx.shared.fs.write(&dst, v.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the list of all implementors for traits
|
||||||
|
let dst = cx.dst.join("implementors");
|
||||||
|
for (&did, imps) in &cx.cache.implementors {
|
||||||
|
// Private modules can leak through to this phase of rustdoc, which
|
||||||
|
// could contain implementations for otherwise private types. In some
|
||||||
|
// rare cases we could find an implementation for an item which wasn't
|
||||||
|
// indexed, so we just skip this step in that case.
|
||||||
|
//
|
||||||
|
// FIXME: this is a vague explanation for why this can't be a `get`, in
|
||||||
|
// theory it should be...
|
||||||
|
let &(ref remote_path, remote_item_type) = match cx.cache.paths.get(&did) {
|
||||||
|
Some(p) => p,
|
||||||
|
None => match cx.cache.external_paths.get(&did) {
|
||||||
|
Some(p) => p,
|
||||||
|
None => continue,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Implementor {
|
||||||
|
text: String,
|
||||||
|
synthetic: bool,
|
||||||
|
types: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let implementors = imps
|
||||||
|
.iter()
|
||||||
|
.filter_map(|imp| {
|
||||||
|
// If the trait and implementation are in the same crate, then
|
||||||
|
// there's no need to emit information about it (there's inlining
|
||||||
|
// going on). If they're in different crates then the crate defining
|
||||||
|
// the trait will be interested in our implementation.
|
||||||
|
//
|
||||||
|
// If the implementation is from another crate then that crate
|
||||||
|
// should add it.
|
||||||
|
if imp.impl_item.def_id.krate == did.krate || !imp.impl_item.def_id.is_local() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Implementor {
|
||||||
|
text: imp.inner_impl().print(cx.cache(), false).to_string(),
|
||||||
|
synthetic: imp.inner_impl().synthetic,
|
||||||
|
types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Only create a js file if we have impls to add to it. If the trait is
|
||||||
|
// documented locally though we always create the file to avoid dead
|
||||||
|
// links.
|
||||||
|
if implementors.is_empty() && !cx.cache.paths.contains_key(&did) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let implementors = format!(
|
||||||
|
r#"implementors["{}"] = {};"#,
|
||||||
|
krate.name,
|
||||||
|
serde_json::to_string(&implementors).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut mydst = dst.clone();
|
||||||
|
for part in &remote_path[..remote_path.len() - 1] {
|
||||||
|
mydst.push(part);
|
||||||
|
}
|
||||||
|
cx.shared.ensure_dir(&mydst)?;
|
||||||
|
mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1]));
|
||||||
|
|
||||||
|
let (mut all_implementors, _) =
|
||||||
|
try_err!(collect(&mydst, &krate.name.as_str(), "implementors"), &mydst);
|
||||||
|
all_implementors.push(implementors);
|
||||||
|
// Sort the implementors by crate so the file will be generated
|
||||||
|
// identically even with rustdoc running in parallel.
|
||||||
|
all_implementors.sort();
|
||||||
|
|
||||||
|
let mut v = String::from("(function() {var implementors = {};\n");
|
||||||
|
for implementor in &all_implementors {
|
||||||
|
writeln!(v, "{}", *implementor).unwrap();
|
||||||
|
}
|
||||||
|
v.push_str(
|
||||||
|
"if (window.register_implementors) {\
|
||||||
|
window.register_implementors(implementors);\
|
||||||
|
} else {\
|
||||||
|
window.pending_implementors = implementors;\
|
||||||
|
}",
|
||||||
|
);
|
||||||
|
v.push_str("})()");
|
||||||
|
cx.shared.fs.write(&mydst, &v)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_minify(
|
||||||
|
fs: &DocFS,
|
||||||
|
dst: PathBuf,
|
||||||
|
contents: &str,
|
||||||
|
enable_minification: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if enable_minification {
|
||||||
|
if dst.extension() == Some(&OsStr::new("css")) {
|
||||||
|
let res = try_none!(minifier::css::minify(contents).ok(), &dst);
|
||||||
|
fs.write(dst, res.as_bytes())
|
||||||
|
} else {
|
||||||
|
fs.write(dst, minifier::js::minify(contents).as_bytes())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fs.write(dst, contents.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,8 +9,10 @@ use crate::clean::*;
|
||||||
use crate::core::DocContext;
|
use crate::core::DocContext;
|
||||||
use crate::fold::DocFolder;
|
use crate::fold::DocFolder;
|
||||||
use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString};
|
use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString};
|
||||||
|
use crate::visit_ast::inherits_doc_hidden;
|
||||||
use rustc_middle::lint::LintLevelSource;
|
use rustc_middle::lint::LintLevelSource;
|
||||||
use rustc_session::lint;
|
use rustc_session::lint;
|
||||||
|
use rustc_span::symbol::sym;
|
||||||
|
|
||||||
crate const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass = Pass {
|
crate const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass = Pass {
|
||||||
name: "check-private-items-doc-tests",
|
name: "check-private-items-doc-tests",
|
||||||
|
@ -51,23 +53,30 @@ impl crate::doctest::Tester for Tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool {
|
crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool {
|
||||||
if matches!(
|
if !cx.cache.access_levels.is_public(item.def_id)
|
||||||
*item.kind,
|
|| matches!(
|
||||||
clean::StructFieldItem(_)
|
*item.kind,
|
||||||
| clean::VariantItem(_)
|
clean::StructFieldItem(_)
|
||||||
| clean::AssocConstItem(_, _)
|
| clean::VariantItem(_)
|
||||||
| clean::AssocTypeItem(_, _)
|
| clean::AssocConstItem(_, _)
|
||||||
| clean::TypedefItem(_, _)
|
| clean::AssocTypeItem(_, _)
|
||||||
| clean::StaticItem(_)
|
| clean::TypedefItem(_, _)
|
||||||
| clean::ConstantItem(_)
|
| clean::StaticItem(_)
|
||||||
| clean::ExternCrateItem(_, _)
|
| clean::ConstantItem(_)
|
||||||
| clean::ImportItem(_)
|
| clean::ExternCrateItem(_, _)
|
||||||
| clean::PrimitiveItem(_)
|
| clean::ImportItem(_)
|
||||||
| clean::KeywordItem(_)
|
| clean::PrimitiveItem(_)
|
||||||
) {
|
| clean::KeywordItem(_)
|
||||||
|
)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.def_id.expect_local());
|
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.def_id.expect_local());
|
||||||
|
if cx.tcx.hir().attrs(hir_id).lists(sym::doc).has_word(sym::hidden)
|
||||||
|
|| inherits_doc_hidden(cx.tcx, hir_id)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let (level, source) = cx.tcx.lint_level_at_node(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id);
|
let (level, source) = cx.tcx.lint_level_at_node(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id);
|
||||||
level != lint::Level::Allow || matches!(source, LintLevelSource::Default)
|
level != lint::Level::Allow || matches!(source, LintLevelSource::Default)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,16 @@ fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> {
|
||||||
std::iter::once(crate_name).chain(relative).collect()
|
std::iter::once(crate_name).chain(relative).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut node: hir::HirId) -> bool {
|
||||||
|
while let Some(id) = tcx.hir().get_enclosing_scope(node) {
|
||||||
|
node = id;
|
||||||
|
if tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
// Also, is there some reason that this doesn't use the 'visit'
|
// Also, is there some reason that this doesn't use the 'visit'
|
||||||
// framework from syntax?.
|
// framework from syntax?.
|
||||||
|
|
||||||
|
@ -158,19 +168,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
|
||||||
om: &mut Module<'tcx>,
|
om: &mut Module<'tcx>,
|
||||||
please_inline: bool,
|
please_inline: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
fn inherits_doc_hidden(cx: &core::DocContext<'_>, mut node: hir::HirId) -> bool {
|
|
||||||
while let Some(id) = cx.tcx.hir().get_enclosing_scope(node) {
|
|
||||||
node = id;
|
|
||||||
if cx.tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if node == hir::CRATE_HIR_ID {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("maybe_inline_local res: {:?}", res);
|
debug!("maybe_inline_local res: {:?}", res);
|
||||||
|
|
||||||
let tcx = self.cx.tcx;
|
let tcx = self.cx.tcx;
|
||||||
|
@ -212,7 +209,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_private = !self.cx.cache.access_levels.is_public(res_did);
|
let is_private = !self.cx.cache.access_levels.is_public(res_did);
|
||||||
let is_hidden = inherits_doc_hidden(self.cx, res_hir_id);
|
let is_hidden = inherits_doc_hidden(self.cx.tcx, res_hir_id);
|
||||||
|
|
||||||
// Only inline if requested or if the item would otherwise be stripped.
|
// Only inline if requested or if the item would otherwise be stripped.
|
||||||
if (!please_inline && !is_private && !is_hidden) || is_no_inline {
|
if (!please_inline && !is_private && !is_hidden) || is_no_inline {
|
||||||
|
|
|
@ -12,16 +12,16 @@
|
||||||
/// ```
|
/// ```
|
||||||
/// println!("hello");
|
/// println!("hello");
|
||||||
/// ```
|
/// ```
|
||||||
fn test() {
|
pub fn test() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
mod module1 { //~ ERROR
|
pub mod module1 { //~ ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(rustdoc::missing_doc_code_examples)]
|
#[allow(rustdoc::missing_doc_code_examples)]
|
||||||
/// doc
|
/// doc
|
||||||
mod module2 {
|
pub mod module2 {
|
||||||
|
|
||||||
/// doc
|
/// doc
|
||||||
pub fn test() {}
|
pub fn test() {}
|
||||||
|
@ -63,9 +63,22 @@ pub enum Enum {
|
||||||
/// Doc
|
/// Doc
|
||||||
//~^ ERROR
|
//~^ ERROR
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
union Union {
|
pub union Union {
|
||||||
/// Doc, but no code example and it's fine!
|
/// Doc, but no code example and it's fine!
|
||||||
a: i32,
|
a: i32,
|
||||||
/// Doc, but no code example and it's fine!
|
/// Doc, but no code example and it's fine!
|
||||||
b: f32,
|
b: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub mod foo {
|
||||||
|
pub fn bar() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn babar() {}
|
||||||
|
|
||||||
|
|
||||||
|
mod fofoo {
|
||||||
|
pub fn tadam() {}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
error: missing code example in this documentation
|
error: missing code example in this documentation
|
||||||
--> $DIR/lint-missing-doc-code-example.rs:19:1
|
--> $DIR/lint-missing-doc-code-example.rs:19:1
|
||||||
|
|
|
|
||||||
LL | / mod module1 {
|
LL | / pub mod module1 {
|
||||||
LL | | }
|
LL | | }
|
||||||
| |_^
|
| |_^
|
||||||
|
|
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Regression test for #81712.
|
||||||
|
|
||||||
|
#![feature(generic_associated_types)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
|
trait A {
|
||||||
|
type BType: B<AType = Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait B {
|
||||||
|
type AType: A<BType = Self>;
|
||||||
|
}
|
||||||
|
trait C {
|
||||||
|
type DType<T>: D<T, CType = Self>;
|
||||||
|
//~^ ERROR: missing generics for associated type `C::DType` [E0107]
|
||||||
|
}
|
||||||
|
trait D<T> {
|
||||||
|
type CType: C<DType = Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,19 @@
|
||||||
|
error[E0107]: missing generics for associated type `C::DType`
|
||||||
|
--> $DIR/issue-81712-cyclic-traits.rs:14:10
|
||||||
|
|
|
||||||
|
LL | type DType<T>: D<T, CType = Self>;
|
||||||
|
| ^^^^^ expected 1 type argument
|
||||||
|
|
|
||||||
|
note: associated type defined here, with 1 type parameter: `T`
|
||||||
|
--> $DIR/issue-81712-cyclic-traits.rs:14:10
|
||||||
|
|
|
||||||
|
LL | type DType<T>: D<T, CType = Self>;
|
||||||
|
| ^^^^^ -
|
||||||
|
help: use angle brackets to add missing type argument
|
||||||
|
|
|
||||||
|
LL | type DType<T><T>: D<T, CType = Self>;
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0107`.
|
Loading…
Add table
Add a link
Reference in a new issue