From 059106e9e16043cd8790f994069401bbf0af2f8d Mon Sep 17 00:00:00 2001 From: Wim Looman <wim@nemo157.com> Date: Thu, 24 Nov 2016 22:01:34 +0100 Subject: [PATCH 1/3] Calculate cfg(feature) requirements Adds a new rustdoc pass that pre-calculates the sets of features required for different items so they can be rendered in the documentation. --- src/librustdoc/clean/mod.rs | 47 +++++++++++++- src/librustdoc/lib.rs | 2 + src/librustdoc/passes/cfg_features.rs | 94 +++++++++++++++++++++++++++ src/librustdoc/passes/mod.rs | 6 ++ 4 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 src/librustdoc/passes/cfg_features.rs diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index c9afa3646b2da..3b499369afd76 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -24,7 +24,7 @@ use syntax::ast; use syntax::attr; use syntax::codemap::Spanned; use syntax::ptr::P; -use syntax::symbol::keywords; +use syntax::symbol::{keywords, InternedString}; use syntax_pos::{self, DUMMY_SP, Pos}; use rustc::middle::const_val::ConstVal; @@ -121,6 +121,7 @@ pub struct Crate { // Only here so that they can be filtered through the rustdoc passes. pub external_traits: FxHashMap<DefId, Trait>, pub masked_crates: FxHashSet<CrateNum>, + pub cfg_feature_map: ConfigFeatureMap, } impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> { @@ -190,6 +191,7 @@ impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> { access_levels: Arc::new(mem::replace(&mut access_levels, Default::default())), external_traits: mem::replace(&mut external_traits, Default::default()), masked_crates, + cfg_feature_map: Default::default(), } } } @@ -3054,3 +3056,46 @@ impl Clean<TypeBinding> for hir::TypeBinding { } } } + +#[derive(Default, Clone, Debug)] +pub struct ConfigFeatureMap { + /// A map from item ids the set of features directly or inherited from + /// parent items that are required for a given item to be included in the + /// output crate. + all: FxHashMap<DefId, FxHashSet<InternedString>>, + + /// A map from item ids to the set of features directly required for the + /// item to be included in the output crate. + introduced: FxHashMap<DefId, FxHashSet<InternedString>>, + + /// A helper for returning empty results. + empty: FxHashSet<InternedString>, +} + +impl ConfigFeatureMap { + pub fn set_all(&mut self, id: DefId, all: FxHashSet<InternedString>) { + if !all.is_empty() { + self.all.insert(id, all); + } + } + + pub fn set_introduced(&mut self, id: DefId, introduced: FxHashSet<InternedString>) { + if !introduced.is_empty() { + self.introduced.insert(id, introduced); + } + } + + pub fn all<'a>(&'a self, id: DefId) -> impl ExactSizeIterator<Item=&'a str> + 'a { + self.all.get(&id) + .unwrap_or(&self.empty) + .iter() + .map(|s| s.as_ref()) + } + + pub fn introduced<'a>(&'a self, id: DefId) -> impl ExactSizeIterator<Item=&'a str> + 'a { + self.introduced.get(&id) + .unwrap_or(&self.empty) + .iter() + .map(|s| s.as_ref()) + } +} diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 9563ccfcc65fd..5f2a52c6963c2 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -24,6 +24,8 @@ #![feature(unicode)] #![feature(vec_remove_item)] #![feature(ascii_ctype)] +#![feature(conservative_impl_trait)] +#![feature(exact_size_is_empty)] extern crate arena; extern crate getopts; diff --git a/src/librustdoc/passes/cfg_features.rs b/src/librustdoc/passes/cfg_features.rs new file mode 100644 index 0000000000000..ed4f8c9e1963d --- /dev/null +++ b/src/librustdoc/passes/cfg_features.rs @@ -0,0 +1,94 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::mem; + +use rustc::util::nodemap::FxHashSet; +use syntax::ast::{LitKind, MetaItem, MetaItemKind}; +use syntax::symbol::InternedString; + +use clean::{self, Item, AttributesExt, ConfigFeatureMap}; +use fold::{self, DocFolder as Underscore}; +use plugins; + +pub fn cfg_features(krate: clean::Crate) -> plugins::PluginResult { + let mut folder = DocFolder::new(); + let mut krate = folder.fold_crate(krate); + krate.cfg_feature_map = folder.map; + krate +} + +struct DocFolder { + map: ConfigFeatureMap, + current: FxHashSet<InternedString>, +} + +impl DocFolder { + fn new() -> DocFolder { + let map = ConfigFeatureMap::default(); + let set = FxHashSet::default(); + DocFolder { + map: map, + current: set, + } + } +} + +impl fold::DocFolder for DocFolder { + fn fold_item(&mut self, item: Item) -> Option<Item> { + let previous = self.current.clone(); + let introduced = { + get_cfg_features_from_item(&item) + .filter(|s| !self.current.contains(s)) + .collect::<FxHashSet<_>>() + }; + self.current.extend(introduced.iter().cloned()); + let item = self.fold_item_recur(item); + let current = mem::replace(&mut self.current, previous); + if let Some(ref item) = item { + if item.def_id.index.as_u32() != 0 { + self.map.set_all(item.def_id, current); + self.map.set_introduced(item.def_id, introduced); + } + } + item + } +} + +fn get_cfg_features_from_item<'a>(item: &'a Item) -> impl Iterator<Item=InternedString> { + let mut cfg_features = Vec::default(); + for attr in item.attrs.lists("cfg") { + let attr = attr.meta_item().unwrap(); + get_cfg_features_from_attr(attr, &mut cfg_features); + } + cfg_features.into_iter() +} + +/// Parses features out of the inner part of a cfg attr, +/// i.e. the `feature = "blah"` part of `#[cfg(feature = "blah")]` +/// +/// Only handles simple cases and (recursively) `all` cases, anything using +/// `any` is ignored as it's unlikely to happen and give a useful result in +/// normal usage. +fn get_cfg_features_from_attr<'a>(attr: &'a MetaItem, cfg_features: &mut Vec<InternedString>) { + match attr.node { + MetaItemKind::List(ref attrs) if attr.name == "all" => { + for attr in attrs.iter().filter_map(|a| a.meta_item()) { + get_cfg_features_from_attr(attr, cfg_features); + } + } + MetaItemKind::NameValue(ref value) if attr.name == "feature" => { + if let LitKind::Str(ref value, _) = value.node { + cfg_features.push(value.as_str()); + } + } + _ => (), + } +} diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index 146629486fabd..d2fc874fe3165 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -36,11 +36,16 @@ pub use self::unindent_comments::unindent_comments; mod propagate_doc_cfg; pub use self::propagate_doc_cfg::propagate_doc_cfg; +pub mod cfg_features; +pub use self::cfg_features::cfg_features; + type Pass = (&'static str, // name fn(clean::Crate) -> plugins::PluginResult, // fn &'static str); // description pub const PASSES: &'static [Pass] = &[ + ("cfg-features", cfg_features, + "indexes all cfg(feature) required for an item"), ("strip-hidden", strip_hidden, "strips all doc(hidden) items from the output"), ("unindent-comments", unindent_comments, @@ -57,6 +62,7 @@ pub const PASSES: &'static [Pass] = &[ ]; pub const DEFAULT_PASSES: &'static [&'static str] = &[ + "cfg-features", "strip-hidden", "strip-private", "collapse-docs", From a0a03d0ec436b0c4f4df724f4abb3c5888427746 Mon Sep 17 00:00:00 2001 From: Wim Looman <wim@nemo157.com> Date: Thu, 24 Nov 2016 22:01:47 +0100 Subject: [PATCH 2/3] wip: Render cfg(feature) Render the features required for an item. Fixes #35903 --- src/librustdoc/html/render.rs | 70 ++++++++++++++++++++++++-- src/librustdoc/html/static/rustdoc.css | 22 ++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 485e75443fe08..cf2d21321dd53 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -61,7 +61,7 @@ use rustc::hir; use rustc::util::nodemap::{FxHashMap, FxHashSet}; use rustc_data_structures::flock; -use clean::{self, AttributesExt, GetDefId, SelfTy, Mutability, Span}; +use clean::{self, AttributesExt, GetDefId, SelfTy, Mutability, Span, ConfigFeatureMap}; use doctree; use fold::DocFolder; use html::escape::Escape; @@ -256,6 +256,10 @@ pub struct Cache { // the access levels from crateanalysis. pub access_levels: Arc<AccessLevels<DefId>>, + /// This map contains information about which #[cfg(feature)]'s are required + /// to be active for a given item to be included in the crate. + pub cfg_feature_map: ConfigFeatureMap, + // Private fields only used when initially crawling a crate to build a cache stack: Vec<String>, @@ -541,6 +545,7 @@ pub fn run(mut krate: clean::Crate, owned_box_did, masked_crates: mem::replace(&mut krate.masked_crates, FxHashSet()), typarams: external_typarams, + cfg_feature_map: mem::replace(&mut krate.cfg_feature_map, Default::default()), }; // Cache where all our extern crates are located @@ -1695,6 +1700,7 @@ impl<'a> fmt::Display for Item<'a> { write!(fmt, "</span>")?; // in-band write!(fmt, "<span class='out-of-band'>")?; + render_all_cfg_features(fmt, self.item.def_id)?; if let Some(version) = self.item.stable_since() { write!(fmt, "<span class='since' title='Stable since Rust version {0}'>{0}</span>", version)?; @@ -2071,6 +2077,9 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context, _ => "", }; + let mut features = String::new(); + render_introduced_cfg_features(&mut features, myitem.def_id)?; + let doc_value = myitem.doc_value().unwrap_or(""); write!(w, " <tr class='{stab} module-item'> @@ -2078,6 +2087,9 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context, title='{title_type} {title}'>{name}</a>{unsafety_flag}</td> <td class='docblock-short'> {stab_docs} {docs} + <span class='out-of-band'> + {features} + </span> </td> </tr>", name = *myitem.name.as_ref().unwrap(), @@ -2089,6 +2101,7 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context, } else { format!("{}", MarkdownSummaryLine(doc_value)) }, + features = features, class = myitem.type_(), stab = myitem.stability_class().unwrap_or("".to_string()), unsafety_flag = unsafety_flag, @@ -2362,6 +2375,9 @@ fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, ns_id = ns_id)?; render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl)?; write!(w, "</code>")?; + write!(w, "<span class='out-of-band'>")?; + render_introduced_cfg_features(w, m.def_id)?; + write!(w, "</span>")?; render_stability_since(w, m, t)?; write!(w, "</span></h3>")?; document(w, cx, m)?; @@ -2502,7 +2518,9 @@ fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, write!(w, ";</span>")?; } } - writeln!(w, "</code></li>")?; + writeln!(w, "</code><span class='out-of-band'>")?; + render_all_cfg_features(w, implementor.def_id)?; + writeln!(w, "</span></li>")?; } } else { // even without any implementations to write in, we still want the heading and list, so the @@ -2584,6 +2602,40 @@ fn render_stability_since_raw<'a>(w: &mut fmt::Formatter, Ok(()) } +fn render_all_cfg_features(w: &mut fmt::Write, def_id: DefId) -> fmt::Result { + let cache = cache(); + + let cfg_features = cache.cfg_feature_map.all(def_id); + if !cfg_features.is_empty() { + let s = if cfg_features.len() == 1 { "" } else { "s" }; + let mut features = cfg_features.fold(String::new(), |acc, f| acc + f + ", "); + features.pop(); + features.pop(); + write!(w, + "<div class='cfg-feature' title='Requires feature{0} {1}'>{1}</div>", + s, features)?; + } + + Ok(()) +} + +fn render_introduced_cfg_features(w: &mut fmt::Write, def_id: DefId) -> fmt::Result { + let cache = cache(); + + let introduced = cache.cfg_feature_map.introduced(def_id); + if !introduced.is_empty() { + let s = if introduced.len() == 1 { "" } else { "s" }; + let mut features = introduced.fold(String::new(), |acc, f| acc + f + ", "); + features.pop(); + features.pop(); + write!(w, + "<div class='cfg-feature cfg-feature-introduced' title='Requires feature{0} {1}'>{1}</div>", + s, features)?; + } + + Ok(()) +} + fn render_stability_since(w: &mut fmt::Formatter, item: &clean::Item, containing_item: &clean::Item) -> fmt::Result { @@ -2708,13 +2760,15 @@ fn item_struct(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, write!(w, "<span id=\"{id}\" class=\"{item_type} small-section-header\"> <a href=\"#{id}\" class=\"anchor field\"></a> <span id=\"{ns_id}\" class='invisible'> - <code>{name}: {ty}</code> - </span></span>", + <code>{name}: {ty}</code>", item_type = ItemType::StructField, id = id, ns_id = ns_id, name = field.name.as_ref().unwrap(), ty = ty)?; + write!(w, "<span class='out-of-band'>")?; + render_introduced_cfg_features(w, field.def_id)?; + write!(w, "</span></span></span>")?; if let Some(stability_class) = field.stability_class() { write!(w, "<span class='stab {stab}'></span>", stab = stability_class)?; @@ -2816,6 +2870,7 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, write!(w, "}}")?; } write!(w, "</pre>")?; + render_introduced_cfg_features(w, it.def_id)?; document(w, cx, it)?; if !e.variants.is_empty() { @@ -2886,6 +2941,7 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, write!(w, "</table></span>")?; } render_stability_since(w, variant, it)?; + render_introduced_cfg_features(w, variant.def_id)?; } } render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)?; @@ -3180,10 +3236,12 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi if let Some(l) = (Item { item: &i.impl_item, cx: cx }).src_href() { write!(w, "<div class='ghost'></div>")?; render_stability_since_raw(w, since, outer_version)?; + render_introduced_cfg_features(w, i.impl_item.def_id)?; write!(w, "<a class='srclink' href='{}' title='{}'>[src]</a>", l, "goto source code")?; } else { render_stability_since_raw(w, since, outer_version)?; + render_introduced_cfg_features(w, i.impl_item.def_id)?; } write!(w, "</span>")?; write!(w, "</h3>\n")?; @@ -3238,6 +3296,9 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi write!(w, "<code>")?; render_assoc_item(w, item, link.anchor(&id), ItemType::Impl)?; write!(w, "</code>")?; + write!(w, "<span class='out-of-band'>")?; + render_introduced_cfg_features(w, item.def_id)?; + write!(w, "</span>")?; if let Some(l) = (Item { cx, item }).src_href() { write!(w, "</span><span class='out-of-band'>")?; write!(w, "<div class='ghost'></div>")?; @@ -3697,6 +3758,7 @@ fn item_macro(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, None, None, None))?; + render_introduced_cfg_features(w, it.def_id)?; document(w, cx, it) } diff --git a/src/librustdoc/html/static/rustdoc.css b/src/librustdoc/html/static/rustdoc.css index 1b7232bf1bca8..8019e7539087c 100644 --- a/src/librustdoc/html/static/rustdoc.css +++ b/src/librustdoc/html/static/rustdoc.css @@ -598,6 +598,19 @@ body.blur > :not(#help) { top: 0; } +.cfg-feature { + padding-left: 10px; + font-family: "Comic Sans MS", monospace; + font-weight: normal; + font-size: initial; + color: purple; + display: inline-block; +} + +.cfg-feature-introduced { + color: red; +} + .variants_table { width: 100%; } @@ -699,12 +712,21 @@ h3 > .collapse-toggle, h4 > .collapse-toggle { height: 12px; } +.cfg-feature + .srclink { + padding-left: 10px; +} + span.since { position: initial; font-size: 20px; margin-right: 5px; } +span.cfg-feature { + position: initial; + margin-right: 5px; +} + .toggle-wrapper > .collapse-toggle { left: 0; } From f83829c8f51301f000fbeb9e5a5e4383da952fd0 Mon Sep 17 00:00:00 2001 From: Wim Looman <wim@nemo157.com> Date: Wed, 4 Oct 2017 20:16:00 +0200 Subject: [PATCH 3/3] fixup! wip: Render cfg(feature) --- src/librustdoc/html/render.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index cf2d21321dd53..029a7d708d394 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -2629,7 +2629,8 @@ fn render_introduced_cfg_features(w: &mut fmt::Write, def_id: DefId) -> fmt::Res features.pop(); features.pop(); write!(w, - "<div class='cfg-feature cfg-feature-introduced' title='Requires feature{0} {1}'>{1}</div>", + "<div class='cfg-feature cfg-feature-introduced' \ + title='Requires feature{0} {1}'>{1}</div>", s, features)?; }