about summary refs log tree commit diff
diff options
context:
space:
mode:
authorkennytm <kennytm@gmail.com>2017-08-05 14:38:52 +0800
committerkennytm <kennytm@gmail.com>2017-08-10 13:43:59 +0800
commita2b888675accccedec7601cc3bd67ca028b4757c (patch)
treea13d255f58f1e86112774af9e8c297871adf5834
parent8f935fbb5b7e8ea5a320082cb9e28095aa0b5759 (diff)
downloadrust-a2b888675accccedec7601cc3bd67ca028b4757c.tar.gz
rust-a2b888675accccedec7601cc3bd67ca028b4757c.zip
Implemented #[doc(cfg(...))].
This attribute has two effects:

1. Items with this attribute and their children will have the "This is
   supported on **** only" message attached in the documentation.

2. The items' doc tests will be skipped if the configuration does not
   match.
-rw-r--r--src/doc/unstable-book/src/language-features/doc-cfg.md42
-rw-r--r--src/librustdoc/clean/cfg.rs889
-rw-r--r--src/librustdoc/clean/mod.rs63
-rw-r--r--src/librustdoc/html/render.rs8
-rw-r--r--src/librustdoc/html/static/styles/main.css1
-rw-r--r--src/librustdoc/lib.rs1
-rw-r--r--src/librustdoc/passes/mod.rs6
-rw-r--r--src/librustdoc/passes/propagate_doc_cfg.rs47
-rw-r--r--src/librustdoc/test.rs10
-rw-r--r--src/libsyntax/feature_gate.rs13
-rw-r--r--src/test/compile-fail/feature-gate-doc_cfg.rs12
-rw-r--r--src/test/rustdoc/doc-cfg.rs47
12 files changed, 1126 insertions, 13 deletions
diff --git a/src/doc/unstable-book/src/language-features/doc-cfg.md b/src/doc/unstable-book/src/language-features/doc-cfg.md
new file mode 100644
index 00000000000..ddc538e1214
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/doc-cfg.md
@@ -0,0 +1,42 @@
+# `doc_cfg`
+
+The tracking issue for this feature is: [#43781]
+
+------
+
+The `doc_cfg` feature allows an API be documented as only available in some specific platforms.
+This attribute has two effects:
+
+1. In the annotated item's documentation, there will be a message saying "This is supported on
+    (platform) only".
+
+2. The item's doc-tests will only run on the specific platform.
+
+This feature was introduced as part of PR [#43348] to allow the platform-specific parts of the
+standard library be documented.
+
+```rust
+#![feature(doc_cfg)]
+
+#[cfg(any(windows, feature = "documentation"))]
+#[doc(cfg(windows))]
+/// The application's icon in the notification area (a.k.a. system tray).
+///
+/// # Examples
+///
+/// ```no_run
+/// extern crate my_awesome_ui_library;
+/// use my_awesome_ui_library::current_app;
+/// use my_awesome_ui_library::windows::notification;
+///
+/// let icon = current_app().get::<notification::Icon>();
+/// icon.show();
+/// icon.show_message("Hello");
+/// ```
+pub struct Icon {
+    // ...
+}
+```
+
+[#43781]: https://github.com/rust-lang/rust/issues/43781
+[#43348]: https://github.com/rust-lang/rust/issues/43348
\ No newline at end of file
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
new file mode 100644
index 00000000000..da8c3a5cf20
--- /dev/null
+++ b/src/librustdoc/clean/cfg.rs
@@ -0,0 +1,889 @@
+// Copyright 2017 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.
+
+//! Representation of a `#[doc(cfg(...))]` attribute.
+
+// FIXME: Once RFC #1868 is implemented, switch to use those structures instead.
+
+use std::mem;
+use std::fmt::{self, Write};
+use std::ops;
+use std::ascii::AsciiExt;
+
+use syntax::symbol::Symbol;
+use syntax::ast::{MetaItem, MetaItemKind, NestedMetaItem, NestedMetaItemKind, LitKind};
+use syntax::parse::ParseSess;
+use syntax::feature_gate::Features;
+
+use syntax_pos::Span;
+
+use html::escape::Escape;
+
+#[derive(Clone, RustcEncodable, RustcDecodable, Debug, PartialEq)]
+pub enum Cfg {
+    /// Accepts all configurations.
+    True,
+    /// Denies all configurations.
+    False,
+    /// A generic configration option, e.g. `test` or `target_os = "linux"`.
+    Cfg(Symbol, Option<Symbol>),
+    /// Negate a configuration requirement, i.e. `not(x)`.
+    Not(Box<Cfg>),
+    /// Union of a list of configuration requirements, i.e. `any(...)`.
+    Any(Vec<Cfg>),
+    /// Intersection of a list of configuration requirements, i.e. `all(...)`.
+    All(Vec<Cfg>),
+}
+
+#[derive(PartialEq, Debug)]
+pub struct InvalidCfgError {
+    pub msg: &'static str,
+    pub span: Span,
+}
+
+impl Cfg {
+    /// Parses a `NestedMetaItem` into a `Cfg`.
+    fn parse_nested(nested_cfg: &NestedMetaItem) -> Result<Cfg, InvalidCfgError> {
+        match nested_cfg.node {
+            NestedMetaItemKind::MetaItem(ref cfg) => Cfg::parse(cfg),
+            NestedMetaItemKind::Literal(ref lit) => Err(InvalidCfgError {
+                msg: "unexpected literal",
+                span: lit.span,
+            }),
+        }
+    }
+
+    /// Parses a `MetaItem` into a `Cfg`.
+    ///
+    /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g. `unix` or
+    /// `target_os = "redox"`.
+    ///
+    /// If the content is not properly formatted, it will return an error indicating what and where
+    /// the error is.
+    pub fn parse(cfg: &MetaItem) -> Result<Cfg, InvalidCfgError> {
+        let name = cfg.name();
+        match cfg.node {
+            MetaItemKind::Word => Ok(Cfg::Cfg(name, None)),
+            MetaItemKind::NameValue(ref lit) => match lit.node {
+                LitKind::Str(value, _) => Ok(Cfg::Cfg(name, Some(value))),
+                _ => Err(InvalidCfgError {
+                    // FIXME: if the main #[cfg] syntax decided to support non-string literals,
+                    // this should be changed as well.
+                    msg: "value of cfg option should be a string literal",
+                    span: lit.span,
+                }),
+            },
+            MetaItemKind::List(ref items) => {
+                let mut sub_cfgs = items.iter().map(Cfg::parse_nested);
+                match &*name.as_str() {
+                    "all" => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)),
+                    "any" => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)),
+                    "not" => if sub_cfgs.len() == 1 {
+                        Ok(!sub_cfgs.next().unwrap()?)
+                    } else {
+                        Err(InvalidCfgError {
+                            msg: "expected 1 cfg-pattern",
+                            span: cfg.span,
+                        })
+                    },
+                    _ => Err(InvalidCfgError {
+                        msg: "invalid predicate",
+                        span: cfg.span,
+                    }),
+                }
+            }
+        }
+    }
+
+    /// Checks whether the given configuration can be matched in the current session.
+    ///
+    /// Equivalent to `attr::cfg_matches`.
+    // FIXME: Actually make use of `features`.
+    pub fn matches(&self, parse_sess: &ParseSess, features: Option<&Features>) -> bool {
+        match *self {
+            Cfg::False => false,
+            Cfg::True => true,
+            Cfg::Not(ref child) => !child.matches(parse_sess, features),
+            Cfg::All(ref sub_cfgs) => {
+                sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(parse_sess, features))
+            },
+            Cfg::Any(ref sub_cfgs) => {
+                sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(parse_sess, features))
+            },
+            Cfg::Cfg(name, value) => parse_sess.config.contains(&(name, value)),
+        }
+    }
+
+    /// Whether the configuration consists of just `Cfg` or `Not`.
+    fn is_simple(&self) -> bool {
+        match *self {
+            Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
+            Cfg::All(..) | Cfg::Any(..) => false,
+        }
+    }
+
+    /// Whether the configuration consists of just `Cfg`, `Not` or `All`.
+    fn is_all(&self) -> bool {
+        match *self {
+            Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
+            Cfg::Any(..) => false,
+        }
+    }
+
+    /// Renders the configuration for human display, as a short HTML description.
+    pub(crate) fn render_short_html(&self) -> String {
+        let mut msg = Html(self).to_string();
+        if self.should_capitalize_first_letter() {
+            if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
+                msg[i .. i+1].make_ascii_uppercase();
+            }
+        }
+        msg
+    }
+
+    /// Renders the configuration for long display, as a long HTML description.
+    pub(crate) fn render_long_html(&self) -> String {
+        let mut msg = format!("This is supported on <strong>{}</strong>", Html(self));
+        if self.should_append_only_to_description() {
+            msg.push_str(" only");
+        }
+        msg.push('.');
+        msg
+    }
+
+    fn should_capitalize_first_letter(&self) -> bool {
+        match *self {
+            Cfg::False | Cfg::True | Cfg::Not(..) => true,
+            Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
+                sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
+            },
+            Cfg::Cfg(name, _) => match &*name.as_str() {
+                "debug_assertions" | "target_endian" => true,
+                _ => false,
+            },
+        }
+    }
+
+    fn should_append_only_to_description(&self) -> bool {
+        match *self {
+            Cfg::False | Cfg::True => false,
+            Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
+            Cfg::Not(ref child) => match **child {
+                Cfg::Cfg(..) => true,
+                _ => false,
+            }
+        }
+    }
+}
+
+impl ops::Not for Cfg {
+    type Output = Cfg;
+    fn not(self) -> Cfg {
+        match self {
+            Cfg::False => Cfg::True,
+            Cfg::True => Cfg::False,
+            Cfg::Not(cfg) => *cfg,
+            s => Cfg::Not(Box::new(s)),
+        }
+    }
+}
+
+impl ops::BitAndAssign for Cfg {
+    fn bitand_assign(&mut self, other: Cfg) {
+        match (self, other) {
+            (&mut Cfg::False, _) | (_, Cfg::True) => {},
+            (s, Cfg::False) => *s = Cfg::False,
+            (s @ &mut Cfg::True, b) => *s = b,
+            (&mut Cfg::All(ref mut a), Cfg::All(ref mut b)) => a.append(b),
+            (&mut Cfg::All(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
+            (s, Cfg::All(mut a)) => {
+                let b = mem::replace(s, Cfg::True);
+                a.push(b);
+                *s = Cfg::All(a);
+            },
+            (s, b) => {
+                let a = mem::replace(s, Cfg::True);
+                *s = Cfg::All(vec![a, b]);
+            },
+        }
+    }
+}
+
+impl ops::BitAnd for Cfg {
+    type Output = Cfg;
+    fn bitand(mut self, other: Cfg) -> Cfg {
+        self &= other;
+        self
+    }
+}
+
+impl ops::BitOrAssign for Cfg {
+    fn bitor_assign(&mut self, other: Cfg) {
+        match (self, other) {
+            (&mut Cfg::True, _) | (_, Cfg::False) => {},
+            (s, Cfg::True) => *s = Cfg::True,
+            (s @ &mut Cfg::False, b) => *s = b,
+            (&mut Cfg::Any(ref mut a), Cfg::Any(ref mut b)) => a.append(b),
+            (&mut Cfg::Any(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
+            (s, Cfg::Any(mut a)) => {
+                let b = mem::replace(s, Cfg::True);
+                a.push(b);
+                *s = Cfg::Any(a);
+            },
+            (s, b) => {
+                let a = mem::replace(s, Cfg::True);
+                *s = Cfg::Any(vec![a, b]);
+            },
+        }
+    }
+}
+
+impl ops::BitOr for Cfg {
+    type Output = Cfg;
+    fn bitor(mut self, other: Cfg) -> Cfg {
+        self |= other;
+        self
+    }
+}
+
+struct Html<'a>(&'a Cfg);
+
+fn write_with_opt_paren<T: fmt::Display>(
+    fmt: &mut fmt::Formatter,
+    has_paren: bool,
+    obj: T,
+) -> fmt::Result {
+    if has_paren {
+        fmt.write_char('(')?;
+    }
+    obj.fmt(fmt)?;
+    if has_paren {
+        fmt.write_char(')')?;
+    }
+    Ok(())
+}
+
+
+impl<'a> fmt::Display for Html<'a> {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        match *self.0 {
+            Cfg::Not(ref child) => match **child {
+                Cfg::Any(ref sub_cfgs) => {
+                    let separator = if sub_cfgs.iter().all(Cfg::is_simple) {
+                        " nor "
+                    } else {
+                        ", nor "
+                    };
+                    for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
+                        fmt.write_str(if i == 0 { "neither " } else { separator })?;
+                        write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg))?;
+                    }
+                    Ok(())
+                }
+                ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Html(simple)),
+                ref c => write!(fmt, "not ({})", Html(c)),
+            },
+
+            Cfg::Any(ref sub_cfgs) => {
+                let separator = if sub_cfgs.iter().all(Cfg::is_simple) {
+                    " or "
+                } else {
+                    ", or "
+                };
+                for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
+                    if i != 0 {
+                        fmt.write_str(separator)?;
+                    }
+                    write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg))?;
+                }
+                Ok(())
+            },
+
+            Cfg::All(ref sub_cfgs) => {
+                for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
+                    if i != 0 {
+                        fmt.write_str(" and ")?;
+                    }
+                    write_with_opt_paren(fmt, !sub_cfg.is_simple(), Html(sub_cfg))?;
+                }
+                Ok(())
+            },
+
+            Cfg::True => fmt.write_str("everywhere"),
+            Cfg::False => fmt.write_str("nowhere"),
+
+            Cfg::Cfg(name, value) => {
+                let n = &*name.as_str();
+                let human_readable = match (n, value) {
+                    ("unix", None) => "Unix",
+                    ("windows", None) => "Windows",
+                    ("debug_assertions", None) => "debug-assertions enabled",
+                    ("target_os", Some(os)) => match &*os.as_str() {
+                        "android" => "Android",
+                        "bitrig" => "Bitrig",
+                        "dragonfly" => "DragonFly BSD",
+                        "emscripten" => "Emscripten",
+                        "freebsd" => "FreeBSD",
+                        "fuchsia" => "Fuchsia",
+                        "haiku" => "Haiku",
+                        "ios" => "iOS",
+                        "l4re" => "L4Re",
+                        "linux" => "Linux",
+                        "macos" => "macOS",
+                        "nacl" => "NaCl",
+                        "netbsd" => "NetBSD",
+                        "openbsd" => "OpenBSD",
+                        "redox" => "Redox",
+                        "solaris" => "Solaris",
+                        "windows" => "Windows",
+                        _ => "",
+                    },
+                    ("target_arch", Some(arch)) => match &*arch.as_str() {
+                        "aarch64" => "AArch64",
+                        "arm" => "ARM",
+                        "asmjs" => "asm.js",
+                        "mips" => "MIPS",
+                        "mips64" => "MIPS-64",
+                        "msp430" => "MSP430",
+                        "powerpc" => "PowerPC",
+                        "powerpc64" => "PowerPC-64",
+                        "s390x" => "s390x",
+                        "sparc64" => "SPARC64",
+                        "wasm32" => "WebAssembly",
+                        "x86" => "x86",
+                        "x86_64" => "x86-64",
+                        _ => "",
+                    },
+                    ("target_vendor", Some(vendor)) => match &*vendor.as_str() {
+                        "apple" => "Apple",
+                        "pc" => "PC",
+                        "rumprun" => "Rumprun",
+                        "sun" => "Sun",
+                        _ => ""
+                    },
+                    ("target_env", Some(env)) => match &*env.as_str() {
+                        "gnu" => "GNU",
+                        "msvc" => "MSVC",
+                        "musl" => "musl",
+                        "newlib" => "Newlib",
+                        "uclibc" => "uClibc",
+                        _ => "",
+                    },
+                    ("target_endian", Some(endian)) => return write!(fmt, "{}-endian", endian),
+                    ("target_pointer_width", Some(bits)) => return write!(fmt, "{}-bit", bits),
+                    _ => "",
+                };
+                if !human_readable.is_empty() {
+                    fmt.write_str(human_readable)
+                } else if let Some(v) = value {
+                    write!(fmt, "<code>{}=\"{}\"</code>", Escape(n), Escape(&*v.as_str()))
+                } else {
+                    write!(fmt, "<code>{}</code>", Escape(n))
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::Cfg;
+
+    use syntax::symbol::Symbol;
+    use syntax::ast::*;
+    use syntax::codemap::dummy_spanned;
+    use syntax_pos::DUMMY_SP;
+
+    fn word_cfg(s: &str) -> Cfg {
+        Cfg::Cfg(Symbol::intern(s), None)
+    }
+
+    fn name_value_cfg(name: &str, value: &str) -> Cfg {
+        Cfg::Cfg(Symbol::intern(name), Some(Symbol::intern(value)))
+    }
+
+    #[test]
+    fn test_cfg_not() {
+        assert_eq!(!Cfg::False, Cfg::True);
+        assert_eq!(!Cfg::True, Cfg::False);
+        assert_eq!(!word_cfg("test"), Cfg::Not(Box::new(word_cfg("test"))));
+        assert_eq!(
+            !Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
+            Cfg::Not(Box::new(Cfg::All(vec![word_cfg("a"), word_cfg("b")])))
+        );
+        assert_eq!(
+            !Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
+            Cfg::Not(Box::new(Cfg::Any(vec![word_cfg("a"), word_cfg("b")])))
+        );
+        assert_eq!(!Cfg::Not(Box::new(word_cfg("test"))), word_cfg("test"));
+    }
+
+    #[test]
+    fn test_cfg_and() {
+        let mut x = Cfg::False;
+        x &= Cfg::True;
+        assert_eq!(x, Cfg::False);
+
+        x = word_cfg("test");
+        x &= Cfg::False;
+        assert_eq!(x, Cfg::False);
+
+        x = word_cfg("test2");
+        x &= Cfg::True;
+        assert_eq!(x, word_cfg("test2"));
+
+        x = Cfg::True;
+        x &= word_cfg("test3");
+        assert_eq!(x, word_cfg("test3"));
+
+        x &= word_cfg("test4");
+        assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4")]));
+
+        x &= word_cfg("test5");
+        assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
+
+        x &= Cfg::All(vec![word_cfg("test6"), word_cfg("test7")]);
+        assert_eq!(x, Cfg::All(vec![
+            word_cfg("test3"),
+            word_cfg("test4"),
+            word_cfg("test5"),
+            word_cfg("test6"),
+            word_cfg("test7"),
+        ]));
+
+        let mut y = Cfg::Any(vec![word_cfg("a"), word_cfg("b")]);
+        y &= x;
+        assert_eq!(y, Cfg::All(vec![
+            word_cfg("test3"),
+            word_cfg("test4"),
+            word_cfg("test5"),
+            word_cfg("test6"),
+            word_cfg("test7"),
+            Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
+        ]));
+
+        assert_eq!(
+            word_cfg("a") & word_cfg("b") & word_cfg("c"),
+            Cfg::All(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
+        );
+    }
+
+    #[test]
+    fn test_cfg_or() {
+        let mut x = Cfg::True;
+        x |= Cfg::False;
+        assert_eq!(x, Cfg::True);
+
+        x = word_cfg("test");
+        x |= Cfg::True;
+        assert_eq!(x, Cfg::True);
+
+        x = word_cfg("test2");
+        x |= Cfg::False;
+        assert_eq!(x, word_cfg("test2"));
+
+        x = Cfg::False;
+        x |= word_cfg("test3");
+        assert_eq!(x, word_cfg("test3"));
+
+        x |= word_cfg("test4");
+        assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4")]));
+
+        x |= word_cfg("test5");
+        assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
+
+        x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("test7")]);
+        assert_eq!(x, Cfg::Any(vec![
+            word_cfg("test3"),
+            word_cfg("test4"),
+            word_cfg("test5"),
+            word_cfg("test6"),
+            word_cfg("test7"),
+        ]));
+
+        let mut y = Cfg::All(vec![word_cfg("a"), word_cfg("b")]);
+        y |= x;
+        assert_eq!(y, Cfg::Any(vec![
+            word_cfg("test3"),
+            word_cfg("test4"),
+            word_cfg("test5"),
+            word_cfg("test6"),
+            word_cfg("test7"),
+            Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
+        ]));
+
+        assert_eq!(
+            word_cfg("a") | word_cfg("b") | word_cfg("c"),
+            Cfg::Any(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
+        );
+    }
+
+    #[test]
+    fn test_parse_ok() {
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::Word,
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::NameValue(dummy_spanned(LitKind::Str(
+                Symbol::intern("done"),
+                StrStyle::Cooked,
+            ))),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(name_value_cfg("all", "done")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("any"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") | word_cfg("b")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(!word_cfg("a")));
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("any"),
+                    node: MetaItemKind::List(vec![
+                        dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                            name: Symbol::intern("a"),
+                            node: MetaItemKind::Word,
+                            span: DUMMY_SP,
+                        })),
+                        dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                            name: Symbol::intern("all"),
+                            node: MetaItemKind::List(vec![
+                                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                                    name: Symbol::intern("b"),
+                                    node: MetaItemKind::Word,
+                                    span: DUMMY_SP,
+                                })),
+                                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                                    name: Symbol::intern("c"),
+                                    node: MetaItemKind::Word,
+                                    span: DUMMY_SP,
+                                })),
+                            ]),
+                            span: DUMMY_SP,
+                        })),
+                    ]),
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(!(word_cfg("a") | (word_cfg("b") & word_cfg("c")))));
+
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("c"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b") & word_cfg("c")));
+    }
+
+    #[test]
+    fn test_parse_err() {
+        let mi = MetaItem {
+            name: Symbol::intern("foo"),
+            node: MetaItemKind::NameValue(dummy_spanned(LitKind::Bool(false))),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("foo"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("all"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("foo"),
+                    node: MetaItemKind::List(vec![]),
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("b"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("any"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("a"),
+                    node: MetaItemKind::Word,
+                    span: DUMMY_SP,
+                })),
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("foo"),
+                    node: MetaItemKind::List(vec![]),
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+
+        let mi = MetaItem {
+            name: Symbol::intern("not"),
+            node: MetaItemKind::List(vec![
+                dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
+                    name: Symbol::intern("foo"),
+                    node: MetaItemKind::List(vec![]),
+                    span: DUMMY_SP,
+                })),
+            ]),
+            span: DUMMY_SP,
+        };
+        assert!(Cfg::parse(&mi).is_err());
+    }
+
+    #[test]
+    fn test_render_short_html() {
+        assert_eq!(
+            word_cfg("unix").render_short_html(),
+            "Unix"
+        );
+        assert_eq!(
+            name_value_cfg("target_os", "macos").render_short_html(),
+            "macOS"
+        );
+        assert_eq!(
+            name_value_cfg("target_pointer_width", "16").render_short_html(),
+            "16-bit"
+        );
+        assert_eq!(
+            name_value_cfg("target_endian", "little").render_short_html(),
+            "Little-endian"
+        );
+        assert_eq!(
+            (!word_cfg("windows")).render_short_html(),
+            "Non-Windows"
+        );
+        assert_eq!(
+            (word_cfg("unix") & word_cfg("windows")).render_short_html(),
+            "Unix and Windows"
+        );
+        assert_eq!(
+            (word_cfg("unix") | word_cfg("windows")).render_short_html(),
+            "Unix or Windows"
+        );
+        assert_eq!(
+            (
+                word_cfg("unix") & word_cfg("windows") & word_cfg("debug_assertions")
+            ).render_short_html(),
+            "Unix and Windows and debug-assertions enabled"
+        );
+        assert_eq!(
+            (
+                word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions")
+            ).render_short_html(),
+            "Unix or Windows or debug-assertions enabled"
+        );
+        assert_eq!(
+            (
+                !(word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions"))
+            ).render_short_html(),
+            "Neither Unix nor Windows nor debug-assertions enabled"
+        );
+        assert_eq!(
+            (
+                (word_cfg("unix") & name_value_cfg("target_arch", "x86_64")) |
+                (word_cfg("windows") & name_value_cfg("target_pointer_width", "64"))
+            ).render_short_html(),
+            "Unix and x86-64, or Windows and 64-bit"
+        );
+        assert_eq!(
+            (!(word_cfg("unix") & word_cfg("windows"))).render_short_html(),
+            "Not (Unix and Windows)"
+        );
+        assert_eq!(
+            (
+                (word_cfg("debug_assertions") | word_cfg("windows")) & word_cfg("unix")
+            ).render_short_html(),
+            "(Debug-assertions enabled or Windows) and Unix"
+        );
+    }
+
+    #[test]
+    fn test_render_long_html() {
+        assert_eq!(
+            word_cfg("unix").render_long_html(),
+            "This is supported on <strong>Unix</strong> only."
+        );
+        assert_eq!(
+            name_value_cfg("target_os", "macos").render_long_html(),
+            "This is supported on <strong>macOS</strong> only."
+        );
+        assert_eq!(
+            name_value_cfg("target_pointer_width", "16").render_long_html(),
+            "This is supported on <strong>16-bit</strong> only."
+        );
+        assert_eq!(
+            name_value_cfg("target_endian", "little").render_long_html(),
+            "This is supported on <strong>little-endian</strong> only."
+        );
+        assert_eq!(
+            (!word_cfg("windows")).render_long_html(),
+            "This is supported on <strong>non-Windows</strong> only."
+        );
+        assert_eq!(
+            (word_cfg("unix") & word_cfg("windows")).render_long_html(),
+            "This is supported on <strong>Unix and Windows</strong> only."
+        );
+        assert_eq!(
+            (word_cfg("unix") | word_cfg("windows")).render_long_html(),
+            "This is supported on <strong>Unix or Windows</strong> only."
+        );
+        assert_eq!(
+            (
+                word_cfg("unix") & word_cfg("windows") & word_cfg("debug_assertions")
+            ).render_long_html(),
+            "This is supported on <strong>Unix and Windows and debug-assertions enabled</strong> \
+                only."
+        );
+        assert_eq!(
+            (
+                word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions")
+            ).render_long_html(),
+            "This is supported on <strong>Unix or Windows or debug-assertions enabled</strong> \
+                only."
+        );
+        assert_eq!(
+            (
+                !(word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions"))
+            ).render_long_html(),
+            "This is supported on <strong>neither Unix nor Windows nor debug-assertions \
+                enabled</strong>."
+        );
+        assert_eq!(
+            (
+                (word_cfg("unix") & name_value_cfg("target_arch", "x86_64")) |
+                (word_cfg("windows") & name_value_cfg("target_pointer_width", "64"))
+            ).render_long_html(),
+            "This is supported on <strong>Unix and x86-64, or Windows and 64-bit</strong> only."
+        );
+        assert_eq!(
+            (!(word_cfg("unix") & word_cfg("windows"))).render_long_html(),
+            "This is supported on <strong>not (Unix and Windows)</strong>."
+        );
+        assert_eq!(
+            (
+                (word_cfg("debug_assertions") | word_cfg("windows")) & word_cfg("unix")
+            ).render_long_html(),
+            "This is supported on <strong>(debug-assertions enabled or Windows) and Unix</strong> \
+                only."
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index a9636c7e2fd..57e72c3a40b 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -52,8 +52,11 @@ use visit_ast;
 use html::item_type::ItemType;
 
 pub mod inline;
+pub mod cfg;
 mod simplify;
 
+use self::cfg::Cfg;
+
 // extract the stability index for a node from tcx, if possible
 fn get_stability(cx: &DocContext, def_id: DefId) -> Option<Stability> {
     cx.tcx.lookup_stability(def_id).clean(cx)
@@ -536,31 +539,67 @@ impl<I: IntoIterator<Item=ast::NestedMetaItem>> NestedAttributesExt for I {
 pub struct Attributes {
     pub doc_strings: Vec<String>,
     pub other_attrs: Vec<ast::Attribute>,
+    pub cfg: Option<Rc<Cfg>>,
     pub span: Option<syntax_pos::Span>,
 }
 
 impl Attributes {
-    pub fn from_ast(attrs: &[ast::Attribute]) -> Attributes {
+    /// Extracts the content from an attribute `#[doc(cfg(content))]`.
+    fn extract_cfg(mi: &ast::MetaItem) -> Option<&ast::MetaItem> {
+        use syntax::ast::NestedMetaItemKind::MetaItem;
+
+        if let ast::MetaItemKind::List(ref nmis) = mi.node {
+            if nmis.len() == 1 {
+                if let MetaItem(ref cfg_mi) = nmis[0].node {
+                    if cfg_mi.check_name("cfg") {
+                        if let ast::MetaItemKind::List(ref cfg_nmis) = cfg_mi.node {
+                            if cfg_nmis.len() == 1 {
+                                if let MetaItem(ref content_mi) = cfg_nmis[0].node {
+                                    return Some(content_mi);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        None
+    }
+
+    pub fn from_ast(diagnostic: &::errors::Handler, attrs: &[ast::Attribute]) -> Attributes {
         let mut doc_strings = vec![];
         let mut sp = None;
+        let mut cfg = Cfg::True;
+
         let other_attrs = attrs.iter().filter_map(|attr| {
             attr.with_desugared_doc(|attr| {
-                if let Some(value) = attr.value_str() {
-                    if attr.check_name("doc") {
-                        doc_strings.push(value.to_string());
-                        if sp.is_none() {
-                            sp = Some(attr.span);
+                if attr.check_name("doc") {
+                    if let Some(mi) = attr.meta() {
+                        if let Some(value) = mi.value_str() {
+                            // Extracted #[doc = "..."]
+                            doc_strings.push(value.to_string());
+                            if sp.is_none() {
+                                sp = Some(attr.span);
+                            }
+                            return None;
+                        } else if let Some(cfg_mi) = Attributes::extract_cfg(&mi) {
+                            // Extracted #[doc(cfg(...))]
+                            match Cfg::parse(cfg_mi) {
+                                Ok(new_cfg) => cfg &= new_cfg,
+                                Err(e) => diagnostic.span_err(e.span, e.msg),
+                            }
+                            return None;
                         }
-                        return None;
                     }
                 }
-
                 Some(attr.clone())
             })
         }).collect();
         Attributes {
-            doc_strings: doc_strings,
-            other_attrs: other_attrs,
+            doc_strings,
+            other_attrs,
+            cfg: if cfg == Cfg::True { None } else { Some(Rc::new(cfg)) },
             span: sp,
         }
     }
@@ -579,8 +618,8 @@ impl AttributesExt for Attributes {
 }
 
 impl Clean<Attributes> for [ast::Attribute] {
-    fn clean(&self, _cx: &DocContext) -> Attributes {
-        Attributes::from_ast(self)
+    fn clean(&self, cx: &DocContext) -> Attributes {
+        Attributes::from_ast(cx.sess().diagnostic(), self)
     }
 }
 
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index fc0adef70ba..95aa8e97dbb 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -1950,6 +1950,14 @@ fn short_stability(item: &clean::Item, cx: &Context, show_reason: bool) -> Vec<S
         stability.push(format!("<div class='stab deprecated'>{}</div>", text))
     }
 
+    if let Some(ref cfg) = item.attrs.cfg {
+        stability.push(format!("<div class='stab portability'>{}</div>", if show_reason {
+            cfg.render_long_html()
+        } else {
+            cfg.render_short_html()
+        }));
+    }
+
     stability
 }
 
diff --git a/src/librustdoc/html/static/styles/main.css b/src/librustdoc/html/static/styles/main.css
index 034c5307fc0..08bf5a10fe9 100644
--- a/src/librustdoc/html/static/styles/main.css
+++ b/src/librustdoc/html/static/styles/main.css
@@ -152,6 +152,7 @@ a.test-arrow {
 
 .stab.unstable { background: #FFF5D6; border-color: #FFC600; }
 .stab.deprecated { background: #F3DFFF; border-color: #7F0087; }
+.stab.portability { background: #C4ECFF; border-color: #7BA5DB; }
 
 #help > div {
 	background: #e9e9e9;
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 64240d26894..9264015ed9e 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -26,6 +26,7 @@
 #![feature(test)]
 #![feature(unicode)]
 #![feature(vec_remove_item)]
+#![feature(ascii_ctype)]
 
 extern crate arena;
 extern crate getopts;
diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs
index 41fd9efe61e..146629486fa 100644
--- a/src/librustdoc/passes/mod.rs
+++ b/src/librustdoc/passes/mod.rs
@@ -33,6 +33,9 @@ pub use self::strip_priv_imports::strip_priv_imports;
 mod unindent_comments;
 pub use self::unindent_comments::unindent_comments;
 
+mod propagate_doc_cfg;
+pub use self::propagate_doc_cfg::propagate_doc_cfg;
+
 type Pass = (&'static str,                                      // name
              fn(clean::Crate) -> plugins::PluginResult,         // fn
              &'static str);                                     // description
@@ -49,6 +52,8 @@ pub const PASSES: &'static [Pass] = &[
       implies strip-priv-imports"),
     ("strip-priv-imports", strip_priv_imports,
      "strips all private import statements (`use`, `extern crate`) from a crate"),
+    ("propagate-doc-cfg", propagate_doc_cfg,
+     "propagates `#[doc(cfg(...))]` to child items"),
 ];
 
 pub const DEFAULT_PASSES: &'static [&'static str] = &[
@@ -56,6 +61,7 @@ pub const DEFAULT_PASSES: &'static [&'static str] = &[
     "strip-private",
     "collapse-docs",
     "unindent-comments",
+    "propagate-doc-cfg",
 ];
 
 
diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs
new file mode 100644
index 00000000000..9e65fff5e2a
--- /dev/null
+++ b/src/librustdoc/passes/propagate_doc_cfg.rs
@@ -0,0 +1,47 @@
+// Copyright 2017 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::rc::Rc;
+
+use clean::{Crate, Item};
+use clean::cfg::Cfg;
+use fold::DocFolder;
+use plugins::PluginResult;
+
+pub fn propagate_doc_cfg(cr: Crate) -> PluginResult {
+    CfgPropagator { parent_cfg: None }.fold_crate(cr)
+}
+
+struct CfgPropagator {
+    parent_cfg: Option<Rc<Cfg>>,
+}
+
+impl DocFolder for CfgPropagator {
+    fn fold_item(&mut self, mut item: Item) -> Option<Item> {
+        let old_parent_cfg = self.parent_cfg.clone();
+
+        let new_cfg = match (self.parent_cfg.take(), item.attrs.cfg.take()) {
+            (None, None) => None,
+            (Some(rc), None) | (None, Some(rc)) => Some(rc),
+            (Some(mut a), Some(b)) => {
+                let b = Rc::try_unwrap(b).unwrap_or_else(|rc| Cfg::clone(&rc));
+                *Rc::make_mut(&mut a) &= b;
+                Some(a)
+            }
+        };
+        self.parent_cfg = new_cfg.clone();
+        item.attrs.cfg = new_cfg;
+
+        let result = self.fold_item_recur(item);
+        self.parent_cfg = old_parent_cfg;
+
+        result
+    }
+}
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index 8e24a3b5879..fff047c99c0 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -125,6 +125,7 @@ pub fn run(input: &str,
         let map = hir::map::map_crate(&mut hir_forest, defs);
         let krate = map.krate();
         let mut hir_collector = HirCollector {
+            sess: &sess,
             collector: &mut collector,
             map: &map
         };
@@ -578,6 +579,7 @@ impl Collector {
 }
 
 struct HirCollector<'a, 'hir: 'a> {
+    sess: &'a session::Session,
     collector: &'a mut Collector,
     map: &'a hir::map::Map<'hir>
 }
@@ -587,12 +589,18 @@ impl<'a, 'hir> HirCollector<'a, 'hir> {
                                             name: String,
                                             attrs: &[ast::Attribute],
                                             nested: F) {
+        let mut attrs = Attributes::from_ast(self.sess.diagnostic(), attrs);
+        if let Some(ref cfg) = attrs.cfg {
+            if !cfg.matches(&self.sess.parse_sess, Some(&self.sess.features.borrow())) {
+                return;
+            }
+        }
+
         let has_name = !name.is_empty();
         if has_name {
             self.collector.names.push(name);
         }
 
-        let mut attrs = Attributes::from_ast(attrs);
         attrs.collapse_doc_comments();
         attrs.unindent_doc_comments();
         if let Some(doc) = attrs.doc_value() {
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index aeb574bc3af..668732e6855 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -364,6 +364,9 @@ declare_features! (
     // global allocators and their internals
     (active, global_allocator, "1.20.0", None),
     (active, allocator_internals, "1.20.0", None),
+
+    // #[doc(cfg(...))]
+    (active, doc_cfg, "1.21.0", Some(43781)),
 );
 
 declare_features! (
@@ -1157,6 +1160,16 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
             self.context.check_attribute(attr, false);
         }
 
+        if attr.check_name("doc") {
+            if let Some(content) = attr.meta_item_list() {
+                if content.len() == 1 && content[0].check_name("cfg") {
+                    gate_feature_post!(&self, doc_cfg, attr.span,
+                        "#[doc(cfg(...))] is experimental"
+                    );
+                }
+            }
+        }
+
         if self.context.features.proc_macro && attr::is_known(attr) {
             return
         }
diff --git a/src/test/compile-fail/feature-gate-doc_cfg.rs b/src/test/compile-fail/feature-gate-doc_cfg.rs
new file mode 100644
index 00000000000..1a77d918014
--- /dev/null
+++ b/src/test/compile-fail/feature-gate-doc_cfg.rs
@@ -0,0 +1,12 @@
+// Copyright 2017 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.
+
+#[doc(cfg(unix))] //~ ERROR: #[doc(cfg(...))] is experimental
+fn main() {}
diff --git a/src/test/rustdoc/doc-cfg.rs b/src/test/rustdoc/doc-cfg.rs
new file mode 100644
index 00000000000..cfb37912fe7
--- /dev/null
+++ b/src/test/rustdoc/doc-cfg.rs
@@ -0,0 +1,47 @@
+// Copyright 2017 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.
+
+#![feature(doc_cfg)]
+
+// @has doc_cfg/struct.Portable.html
+// @!has - '//*[@id="main"]/*[@class="stability"]/*[@class="stab portability"]' ''
+// @has - '//*[@id="method.unix_and_arm_only_function"]' 'fn unix_and_arm_only_function()'
+// @has - '//*[@class="stab portability"]' 'This is supported on Unix and ARM only.'
+pub struct Portable;
+
+// @has doc_cfg/unix_only/index.html \
+//  '//*[@id="main"]/*[@class="stability"]/*[@class="stab portability"]' \
+//  'This is supported on Unix only.'
+// @matches - '//*[@class=" module-item"]//*[@class="stab portability"]' '\AUnix\Z'
+// @matches - '//*[@class=" module-item"]//*[@class="stab portability"]' '\AUnix and ARM\Z'
+// @count - '//*[@class="stab portability"]' 3
+#[doc(cfg(unix))]
+pub mod unix_only {
+    // @has doc_cfg/unix_only/fn.unix_only_function.html \
+    //  '//*[@id="main"]/*[@class="stability"]/*[@class="stab portability"]' \
+    //  'This is supported on Unix only.'
+    // @count - '//*[@class="stab portability"]' 1
+    pub fn unix_only_function() {
+        content::should::be::irrelevant();
+    }
+
+    // @has doc_cfg/unix_only/trait.ArmOnly.html \
+    //  '//*[@id="main"]/*[@class="stability"]/*[@class="stab portability"]' \
+    //  'This is supported on Unix and ARM only.'
+    // @count - '//*[@class="stab portability"]' 2
+    #[doc(cfg(target_arch = "arm"))]
+    pub trait ArmOnly {
+        fn unix_and_arm_only_function();
+    }
+
+    impl ArmOnly for super::Portable {
+        fn unix_and_arm_only_function() {}
+    }
+}
\ No newline at end of file