Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/doc/rustdoc/src/unstable-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -1121,3 +1121,25 @@ pub struct S;
// There will be no mention of `feature = "a"` in the documentation.
pub use dep::S as Y;
```

## `--feature-documentation`: Generate documentation for a build (cfg) feature

This command line flag allows to add documentation for a build (cfg) feature. For example if you
have in your code:

```rust
#[cfg(feature = "something")]
pub struct X;
```

You can add the documentation of the `something` feature like this:

```console
rustdoc --feature-documentation 'something=This feature gives access to the `X` struct'
```

The name of the feature and its documentation must be separated with a `=` character. If you put
nothing after the `=` character, the feature will still be present in the documentation, but with
no associated documentation.

For now, intra-doc links don't work with the features documentation.
14 changes: 12 additions & 2 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -716,11 +716,15 @@ impl Item {
/// * `ItemType::Primitive`
/// * `ItemType::Keyword`
/// * `ItemType::Attribute`
/// * `ItemType::Feature`
///
/// They are considered fake because they only exist thanks to their
/// `#[doc(primitive|keyword|attribute)]` attribute.
pub(crate) fn is_fake_item(&self) -> bool {
matches!(self.type_(), ItemType::Primitive | ItemType::Keyword | ItemType::Attribute)
matches!(
self.type_(),
ItemType::Primitive | ItemType::Keyword | ItemType::Attribute | ItemType::Feature
)
}
pub(crate) fn is_stripped(&self) -> bool {
match self.kind {
Expand Down Expand Up @@ -886,7 +890,10 @@ impl Item {
// Primitives and Keywords are written in the source code as private modules.
// The modules need to be private so that nobody actually uses them, but the
// keywords and primitives that they are documenting are public.
ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => {
ItemKind::KeywordItem
| ItemKind::PrimitiveItem(_)
| ItemKind::AttributeItem
| ItemKind::FeatureItem => {
return Some(Visibility::Public);
}
// Variant fields inherit their enum's visibility.
Expand Down Expand Up @@ -994,6 +1001,8 @@ pub(crate) enum ItemKind {
/// This item represents an anonymous constant with a `#[doc(attribute = "...")]` attribute which is used
/// to generate documentation for Rust builtin attributes.
AttributeItem,
/// This item represents a `cfg` documented feature passed through the command line.
FeatureItem,
}

impl ItemKind {
Expand Down Expand Up @@ -1036,6 +1045,7 @@ impl ItemKind {
| StrippedItem(_)
| KeywordItem
| AttributeItem
| FeatureItem
| PlaceholderImplItem => [].iter(),
}
}
Expand Down
49 changes: 44 additions & 5 deletions src/librustdoc/clean/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use rustc_hir::find_attr;
use rustc_metadata::rendered_const;
use rustc_middle::mir;
use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, TyCtxt, TypeVisitableExt};
use rustc_resolve::rustdoc::DocFragment;
use rustc_span::def_id::ModId;
use rustc_span::symbol::{Symbol, kw, sym};
use tracing::{debug, warn};
Expand All @@ -24,19 +25,20 @@ use crate::clean::auto_trait::synthesize_auto_trait_impls;
use crate::clean::blanket_impl::synthesize_blanket_impls;
use crate::clean::render_macro_matchers::render_macro_matcher;
use crate::clean::{
AssocItemConstraint, AssocItemConstraintKind, Crate, ExternalCrate, Generic, GenericArg,
GenericArgs, ImportSource, Item, ItemKind, Lifetime, Path, PathSegment, Primitive,
PrimitiveType, Term, Type, clean_doc_module, clean_middle_const, clean_middle_region,
clean_middle_ty, inline,
AssocItemConstraint, AssocItemConstraintKind, Attributes, Crate, ExternalCrate, Generic,
GenericArg, GenericArgs, ImportSource, Item, ItemId, ItemInner, ItemKind, Lifetime, Module,
Path, PathSegment, Primitive, PrimitiveType, Term, Type, clean_doc_module, clean_middle_const,
clean_middle_region, clean_middle_ty, inline,
};
use crate::config::Feature;
use crate::core::DocContext;
use crate::display::Joined as _;
use crate::formats::item_type::ItemType;

#[cfg(test)]
mod tests;

pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
pub(crate) fn krate(cx: &mut DocContext<'_>, features: Vec<Feature>) -> Crate {
let module = crate::visit_ast::RustdocVisitor::new(cx).visit();

// Clean the crate, translating the entire librustc_ast AST to one that is
Expand Down Expand Up @@ -83,11 +85,48 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
m.items.extend(documented_attributes.into_iter().map(|(def_id, kw)| {
Item::from_def_id_and_parts(def_id, Some(kw), ItemKind::AttributeItem, cx.tcx)
}));
clean_features(m, features);
}

Crate { module, external_traits: Box::new(mem::take(&mut cx.external_traits)) }
}

fn clean_features(module: &mut Module, features: Vec<Feature>) {
for feature in features {
module.items.push(clean_feature(feature));
}
}

fn clean_feature(Feature { name, documentation }: Feature) -> Item {
use rustc_ast::token::{CommentKind, DocFragmentKind};

let item = ItemInner {
name: Some(Symbol::intern(&name)),
kind: ItemKind::FeatureItem,
attrs: Attributes {
// We need to convert the command line argument into something rustdoc and rustc
// understand.
doc_strings: vec![DocFragment {
span: rustc_span::DUMMY_SP,
item_id: None,
doc: documentation
.as_ref()
.map(|doc| Symbol::intern(doc))
.unwrap_or(rustc_span::symbol::sym::empty),
kind: DocFragmentKind::Sugared(CommentKind::Block),
indent: 0,
from_expansion: false,
}],
other_attrs: ThinVec::new(),
},
stability: None,
item_id: ItemId::from(rustc_hir::def_id::CRATE_DEF_ID.to_def_id()),
inline_stmt_id: None,
cfg: None,
};
Item { inner: Box::new(item) }
}

pub(crate) fn clean_middle_generic_args<'tcx>(
cx: &mut DocContext<'tcx>,
args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>,
Expand Down
52 changes: 52 additions & 0 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ pub(crate) enum MergeDoctests {
Auto,
}

#[derive(Clone, Debug)]
pub(crate) struct Feature {
pub(crate) name: String,
pub(crate) documentation: Option<String>,
}

/// Configuration options for rustdoc.
#[derive(Clone)]
pub(crate) struct Options {
Expand Down Expand Up @@ -172,6 +178,9 @@ pub(crate) struct Options {

/// Target modifiers.
pub(crate) target_modifiers: BTreeMap<OptionsTargetModifiers, String>,

/// Documentation for crate features.
pub(crate) documented_features: Vec<Feature>,
}

impl fmt::Debug for Options {
Expand Down Expand Up @@ -218,6 +227,7 @@ impl fmt::Debug for Options {
.field("no_capture", &self.no_capture)
.field("scrape_examples_options", &self.scrape_examples_options)
.field("unstable_features", &self.unstable_features)
.field("documented_features", &self.documented_features)
.finish()
}
}
Expand Down Expand Up @@ -896,6 +906,8 @@ impl Options {

let disable_minification = matches.opt_present("disable-minification");

let documented_features = parse_feature_documentation(matches, dcx);

let options = Options {
bin_crate,
proc_macro_crate,
Expand Down Expand Up @@ -941,6 +953,7 @@ impl Options {
unstable_features,
doctest_build_args,
target_modifiers: collected_options.target_modifiers,
documented_features,
};
let render_options = RenderOptions {
output,
Expand Down Expand Up @@ -1132,3 +1145,42 @@ fn parse_merge_doctests(
}
}
}

const OPT_NAME: &str = "feature-documentation";

fn remove_wrapping_quotes<'a>(
dcx: DiagCtxtHandle<'_>,
full: &str,
s: &'a str,
c: char,
) -> Option<&'a str> {
let Some(s) = s.strip_prefix(c) else { return None };
let Some(s) = s.strip_suffix(c) else {
dcx.fatal(format!("unclosed documentation string for `--{OPT_NAME}` in {full:?}"));
};
Some(s.trim())
}

fn parse_feature_documentation(m: &getopts::Matches, dcx: DiagCtxtHandle<'_>) -> Vec<Feature> {
let entries = m.opt_strs(OPT_NAME);
let mut features: Vec<Feature> = Vec::with_capacity(entries.len());
for entry in entries {
let Some((name, doc)) = entry.split_once('=') else {
dcx.fatal(format!(
"invalid argument for `--{OPT_NAME}`: expected `name=doc`, found {entry:?}",
));
};
if features.iter().any(|feature| feature.name == name) {
dcx.fatal(format!("feature {name:?} is passed more than once in `--{OPT_NAME}`"));
}
let doc = doc.trim();
let doc = remove_wrapping_quotes(dcx, &entry, doc, '"')
.or_else(|| remove_wrapping_quotes(dcx, &entry, doc, '\''))
.unwrap_or(doc);
features.push(Feature {
name: name.to_string(),
documentation: if doc.is_empty() { None } else { Some(doc.to_string()) },
});
}
features
}
3 changes: 2 additions & 1 deletion src/librustdoc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ pub(crate) fn run_global_ctxt(
show_coverage: bool,
render_options: RenderOptions,
output_format: OutputFormat,
documented_features: Vec<crate::config::Feature>,
) -> (clean::Crate, RenderOptions, Cache, FxHashMap<rustc_span::BytePos, Vec<ExpandedCode>>) {
// Certain queries assume that some checks were run elsewhere
// (see https://github.com/rust-lang/rust/pull/73566#issuecomment-656954425),
Expand Down Expand Up @@ -407,7 +408,7 @@ pub(crate) fn run_global_ctxt(
ctxt.external_traits.insert(sized_trait_did, sized_trait);
}

let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt));
let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt, documented_features));

if krate.module.doc_value().is_empty() {
let help = format!(
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub(crate) trait DocFolder: Sized {
| AssocTypeItem(..)
| KeywordItem
| AttributeItem
| FeatureItem
| PlaceholderImplItem => kind,
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/librustdoc/formats/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ impl DocFolder for CacheBuilder<'_, '_> {
| clean::AssocTypeItem(..)
| clean::StrippedItem(..)
| clean::KeywordItem
| clean::FeatureItem
| clean::AttributeItem => {
// FIXME: Do these need handling?
// The person writing this comment doesn't know.
Expand Down Expand Up @@ -577,6 +578,9 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It
}
}
}
// Features have the `DefId` of the crate, so if we go in the condition below, they'll be
// ignored.
clean::FeatureItem => (None, &*cache.stack),
_ => {
// Don't index if item is crate root, which is inserted later on when serializing the index.
// Don't index if containing module is stripped (i.e., private),
Expand Down
3 changes: 3 additions & 0 deletions src/librustdoc/formats/item_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ item_type! {
// still having the filtering working as expected.
DeclMacroAttribute = 28,
DeclMacroDerive = 29,
Feature = 30,
}

impl<'a> From<&'a clean::Item> for ItemType {
Expand Down Expand Up @@ -150,6 +151,7 @@ impl<'a> From<&'a clean::Item> for ItemType {
MacroKind::Attr => ItemType::ProcAttribute,
MacroKind::Derive => ItemType::ProcDerive,
},
clean::FeatureItem => ItemType::Feature,
clean::StrippedItem(..) => unreachable!(),
}
}
Expand Down Expand Up @@ -230,6 +232,7 @@ impl ItemType {
ItemType::ProcDerive | ItemType::DeclMacroDerive => "derive",
ItemType::TraitAlias => "traitalias",
ItemType::Attribute => "attribute",
ItemType::Feature => "feature",
}
}
pub(crate) fn is_method(&self) -> bool {
Expand Down
5 changes: 5 additions & 0 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2612,6 +2612,7 @@ pub(crate) enum ItemSection {
AttributeMacros,
DeriveMacros,
TraitAliases,
Features,
}

impl ItemSection {
Expand All @@ -2620,6 +2621,7 @@ impl ItemSection {
// NOTE: The order here affects the order in the UI.
// Keep this synchronized with addSidebarItems in main.js
&[
Features,
Reexports,
PrimitiveTypes,
Modules,
Expand Down Expand Up @@ -2675,6 +2677,7 @@ impl ItemSection {
Self::AttributeMacros => "attributes",
Self::DeriveMacros => "derives",
Self::TraitAliases => "trait-aliases",
Self::Features => "features",
}
}

Expand Down Expand Up @@ -2705,6 +2708,7 @@ impl ItemSection {
Self::AttributeMacros => "Attribute Macros",
Self::DeriveMacros => "Derive Macros",
Self::TraitAliases => "Trait Aliases",
Self::Features => "Features",
}
}
}
Expand Down Expand Up @@ -2736,6 +2740,7 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection {
ItemType::ProcAttribute | ItemType::DeclMacroAttribute => ItemSection::AttributeMacros,
ItemType::ProcDerive | ItemType::DeclMacroDerive => ItemSection::DeriveMacros,
ItemType::TraitAlias => ItemSection::TraitAliases,
ItemType::Feature => ItemSection::Features,
}
}

Expand Down
Loading
Loading