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

View file

@ -499,7 +499,7 @@ impl<'tcx> Instance<'tcx> {
}
/// 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 {
debug!("polymorphize: running polymorphization analysis");
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),
// 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.
_ => 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::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
/// `def_id`. Returns a bitset where bits representing unused parameters are set (`is_empty`
/// indicates all parameters are used).
#[instrument(skip(tcx))]
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 polymorphization disabled, then all parameters are used.
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);
debug!("unused_generic_params: generics={:?}", generics);
debug!(?generics);
// Exit early when there are no parameters to be unused.
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());
match context {
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();
}
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();
}
_ => {}
@ -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`");
let mut unused_parameters = FiniteBitSet::<u32>::new_empty();
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);
debug!("unused_generic_params: (after default) unused_parameters={:?}", unused_parameters);
debug!(?unused_parameters, "(after default)");
// Visit MIR and accumululate used generic parameters.
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 };
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);
debug!("unused_generic_params: (end) unused_parameters={:?}", unused_parameters);
debug!(?unused_parameters, "(end)");
// Emit errors for debugging and testing if enabled.
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
/// 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.
#[instrument(skip(tcx, def_id, generics, unused_parameters))]
fn mark_used_by_default_parameters<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
generics: &'tcx ty::Generics,
unused_parameters: &mut FiniteBitSet<u32>,
) {
if !tcx.is_trait(def_id) && (tcx.is_closure(def_id) || tcx.type_of(def_id).is_generator()) {
for param in &generics.params {
debug!("mark_used_by_default_parameters: (closure/gen) param={:?}", param);
unused_parameters.clear(param.index);
}
} else {
for param in &generics.params {
debug!("mark_used_by_default_parameters: (other) param={:?}", param);
if let ty::GenericParamDefKind::Lifetime = param.kind {
match tcx.def_kind(def_id) {
DefKind::Closure | DefKind::Generator => {
for param in &generics.params {
debug!(?param, "(closure/gen)");
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 {
@ -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
/// those as used.
#[instrument(skip(tcx, def_id))]
fn mark_used_by_predicates<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
@ -135,16 +166,12 @@ fn mark_used_by_predicates<'tcx>(
) {
let def_id = tcx.closure_base_def_id(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();
// 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.
while current_unused_parameters != *unused_parameters {
debug!(
"mark_used_by_predicates: current_unused_parameters={:?} = unused_parameters={:?}",
current_unused_parameters, unused_parameters
);
debug!(?current_unused_parameters, ?unused_parameters);
current_unused_parameters = *unused_parameters;
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
/// parameter which was unused.
#[instrument(skip(tcx, generics))]
fn emit_unused_generic_params_error<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
generics: &'tcx ty::Generics,
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);
if !tcx
.get_attrs(base_def_id)
@ -185,7 +212,6 @@ fn emit_unused_generic_params_error<'tcx>(
return;
}
debug!("emit_unused_generic_params_error: unused_parameters={:?}", unused_parameters);
let fn_span = match tcx.opt_item_name(def_id) {
Some(ident) => ident.span,
_ => tcx.def_span(def_id),
@ -197,7 +223,7 @@ fn emit_unused_generic_params_error<'tcx>(
while let Some(generics) = next_generics {
for param in &generics.params {
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);
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> {
/// Invoke `unused_generic_params` on a body contained within the current item (e.g.
/// a closure, generator or constant).
#[instrument(skip(self, def_id, substs))]
fn visit_child_body(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) {
let unused = self.tcx.unused_generic_params(def_id);
debug!(
"visit_child_body: unused_parameters={:?} unused={:?}",
self.unused_parameters, unused
);
debug!(?self.unused_parameters, ?unused);
for (i, arg) in substs.iter().enumerate() {
let i = i.try_into().unwrap();
if !unused.contains(i).unwrap_or(false) {
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> {
#[instrument(skip(self, local))]
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) {
let def_kind = self.tcx.def_kind(self.def_id);
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
// that will call `visit_substs`, resulting in each generic parameter captured being
// considered used by default.
debug!("visit_local_decl: skipping closure substs");
debug!("skipping closure substs");
return;
}
}
@ -263,15 +287,15 @@ impl<'a, 'tcx> Visitor<'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> {
debug!("visit_const: c={:?}", c);
if !c.has_param_types_or_consts() {
return ControlFlow::CONTINUE;
}
match c.val {
ty::ConstKind::Param(param) => {
debug!("visit_const: param={:?}", param);
debug!(?param);
self.unused_parameters.clear(param.index);
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> {
debug!("visit_ty: ty={:?}", ty);
if !ty.has_param_types_or_consts() {
return ControlFlow::CONTINUE;
}
match *ty.kind() {
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.
if def_id == self.def_id {
return ControlFlow::CONTINUE;
@ -316,7 +340,7 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
ControlFlow::CONTINUE
}
ty::Param(param) => {
debug!("visit_ty: param={:?}", param);
debug!(?param);
self.unused_parameters.clear(param.index);
ControlFlow::CONTINUE
}
@ -333,8 +357,8 @@ struct HasUsedGenericParams<'a> {
impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> {
type BreakTy = ();
#[instrument(skip(self))]
fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow<Self::BreakTy> {
debug!("visit_const: c={:?}", c);
if !c.has_param_types_or_consts() {
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> {
debug!("visit_ty: ty={:?}", ty);
if !ty.has_param_types_or_consts() {
return ControlFlow::CONTINUE;
}

View file

@ -8,7 +8,7 @@
- [Documentation tests](documentation-tests.md)
- [Linking to items by name](linking-to-items-by-name.md)
- [Lints](lints.md)
- [Passes](passes.md)
- [Advanced features](advanced-features.md)
- [Unstable features](unstable-features.md)
- [Passes](passes.md)
- [References](references.md)

View file

@ -57,23 +57,6 @@ release: 1.17.0
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
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
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
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
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
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
an empty option. The file can use Unix or Windows style line endings, and must be
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` 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)
* [`--no-defaults`](command-line-arguments.md#--no-defaults-dont-run-default-passes)
## 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.
In the past the most common use case for customizing passes was to omit the `strip-private` pass.
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).

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
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
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]
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::fold::DocFolder;
use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString};
use crate::visit_ast::inherits_doc_hidden;
use rustc_middle::lint::LintLevelSource;
use rustc_session::lint;
use rustc_span::symbol::sym;
crate const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass = Pass {
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 {
if matches!(
*item.kind,
clean::StructFieldItem(_)
| clean::VariantItem(_)
| clean::AssocConstItem(_, _)
| clean::AssocTypeItem(_, _)
| clean::TypedefItem(_, _)
| clean::StaticItem(_)
| clean::ConstantItem(_)
| clean::ExternCrateItem(_, _)
| clean::ImportItem(_)
| clean::PrimitiveItem(_)
| clean::KeywordItem(_)
) {
if !cx.cache.access_levels.is_public(item.def_id)
|| matches!(
*item.kind,
clean::StructFieldItem(_)
| clean::VariantItem(_)
| clean::AssocConstItem(_, _)
| clean::AssocTypeItem(_, _)
| clean::TypedefItem(_, _)
| clean::StaticItem(_)
| clean::ConstantItem(_)
| clean::ExternCrateItem(_, _)
| clean::ImportItem(_)
| clean::PrimitiveItem(_)
| clean::KeywordItem(_)
)
{
return false;
}
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);
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()
}
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'
// framework from syntax?.
@ -158,19 +168,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
om: &mut Module<'tcx>,
please_inline: 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);
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_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.
if (!please_inline && !is_private && !is_hidden) || is_no_inline {

View file

@ -12,16 +12,16 @@
/// ```
/// println!("hello");
/// ```
fn test() {
pub fn test() {
}
#[allow(missing_docs)]
mod module1 { //~ ERROR
pub mod module1 { //~ ERROR
}
#[allow(rustdoc::missing_doc_code_examples)]
/// doc
mod module2 {
pub mod module2 {
/// doc
pub fn test() {}
@ -63,9 +63,22 @@ pub enum Enum {
/// Doc
//~^ ERROR
#[repr(C)]
union Union {
pub union Union {
/// Doc, but no code example and it's fine!
a: i32,
/// Doc, but no code example and it's fine!
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
--> $DIR/lint-missing-doc-code-example.rs:19:1
|
LL | / mod module1 {
LL | / pub mod module1 {
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`.