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)?;
     }