1
Fork 0

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:
bors 2021-03-05 09:28:07 +00:00
commit 8ccc89bc31
19 changed files with 2827 additions and 2751 deletions

View file

@ -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() {

View file

@ -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 {

View file

@ -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(..)

View file

@ -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;
} }

View file

@ -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)

View file

@ -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.

View file

@ -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.

View file

@ -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:

View 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

File diff suppressed because it is too large Load diff

View file

@ -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() {

View 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())
}
}

View file

@ -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)
} }

View file

@ -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 {

View file

@ -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() {}
}

View file

@ -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 | | }
| |_^ | |_^
| |

View file

@ -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() {}

View file

@ -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`.