From 8f935fbb5b7e8ea5a320082cb9e28095aa0b5759 Mon Sep 17 00:00:00 2001 From: kennytm Date: Wed, 19 Jul 2017 16:09:22 +0800 Subject: [PATCH 1/4] Strip out function implementation when documenting. This prevents compilation failure we want to document a platform-specific module. Every function is replaced by `loop {}` using the same construct as `--unpretty everybody_loops`. Note also a workaround to #43636 is included: `const fn` will retain their bodies, since the standard library has quite a number of them. --- src/librustc_driver/pretty.rs | 62 ++++++++++++++++++----------------- src/librustdoc/core.rs | 3 ++ src/librustdoc/test.rs | 4 +++ 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/librustc_driver/pretty.rs b/src/librustc_driver/pretty.rs index 269363fdd2f98..ef6a4b2092901 100644 --- a/src/librustc_driver/pretty.rs +++ b/src/librustc_driver/pretty.rs @@ -44,6 +44,7 @@ use std::io::{self, Write}; use std::option; use std::path::Path; use std::str::FromStr; +use std::mem; use rustc::hir::map as hir_map; use rustc::hir::map::blocks; @@ -618,52 +619,53 @@ impl UserIdentifiedItem { } } -struct ReplaceBodyWithLoop { +// Note: Also used by librustdoc, see PR #43348. Consider moving this struct elsewhere. +pub struct ReplaceBodyWithLoop { within_static_or_const: bool, } impl ReplaceBodyWithLoop { - fn new() -> ReplaceBodyWithLoop { + pub fn new() -> ReplaceBodyWithLoop { ReplaceBodyWithLoop { within_static_or_const: false } } + + fn run R>(&mut self, is_const: bool, action: F) -> R { + let old_const = mem::replace(&mut self.within_static_or_const, is_const); + let ret = action(self); + self.within_static_or_const = old_const; + ret + } } impl fold::Folder for ReplaceBodyWithLoop { fn fold_item_kind(&mut self, i: ast::ItemKind) -> ast::ItemKind { - match i { - ast::ItemKind::Static(..) | - ast::ItemKind::Const(..) => { - self.within_static_or_const = true; - let ret = fold::noop_fold_item_kind(i, self); - self.within_static_or_const = false; - return ret; - } - _ => fold::noop_fold_item_kind(i, self), - } + let is_const = match i { + ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => true, + ast::ItemKind::Fn(_, _, ref constness, _, _, _) => + constness.node == ast::Constness::Const, + _ => false, + }; + self.run(is_const, |s| fold::noop_fold_item_kind(i, s)) } fn fold_trait_item(&mut self, i: ast::TraitItem) -> SmallVector { - match i.node { - ast::TraitItemKind::Const(..) => { - self.within_static_or_const = true; - let ret = fold::noop_fold_trait_item(i, self); - self.within_static_or_const = false; - return ret; - } - _ => fold::noop_fold_trait_item(i, self), - } + let is_const = match i.node { + ast::TraitItemKind::Const(..) => true, + ast::TraitItemKind::Method(ast::MethodSig { ref constness, .. }, _) => + constness.node == ast::Constness::Const, + _ => false, + }; + self.run(is_const, |s| fold::noop_fold_trait_item(i, s)) } fn fold_impl_item(&mut self, i: ast::ImplItem) -> SmallVector { - match i.node { - ast::ImplItemKind::Const(..) => { - self.within_static_or_const = true; - let ret = fold::noop_fold_impl_item(i, self); - self.within_static_or_const = false; - return ret; - } - _ => fold::noop_fold_impl_item(i, self), - } + let is_const = match i.node { + ast::ImplItemKind::Const(..) => true, + ast::ImplItemKind::Method(ast::MethodSig { ref constness, .. }, _) => + constness.node == ast::Constness::Const, + _ => false, + }; + self.run(is_const, |s| fold::noop_fold_impl_item(i, s)) } fn fold_block(&mut self, b: P) -> P { diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index e101e29fc6fbb..9bb7e4e3a09d5 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -10,6 +10,7 @@ use rustc_lint; use rustc_driver::{driver, target_features, abort_on_err}; +use rustc_driver::pretty::ReplaceBodyWithLoop; use rustc::dep_graph::DepGraph; use rustc::session::{self, config}; use rustc::hir::def_id::DefId; @@ -26,6 +27,7 @@ use rustc_metadata::cstore::CStore; use syntax::{ast, codemap}; use syntax::feature_gate::UnstableFeatures; +use syntax::fold::Folder; use errors; use errors::emitter::ColorConfig; @@ -158,6 +160,7 @@ pub fn run_core(search_paths: SearchPaths, let krate = panictry!(driver::phase_1_parse_input(&driver::CompileController::basic(), &sess, &input)); + let krate = ReplaceBodyWithLoop::new().fold_crate(krate); let name = link::find_crate_name(Some(&sess), &krate.attrs, &input); diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index b1e92b5190f37..8e24a3b587920 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -32,6 +32,7 @@ use rustc_back::dynamic_lib::DynamicLibrary; use rustc_back::tempdir::TempDir; use rustc_driver::{self, driver, Compilation}; use rustc_driver::driver::phase_2_configure_and_expand; +use rustc_driver::pretty::ReplaceBodyWithLoop; use rustc_metadata::cstore::CStore; use rustc_resolve::MakeGlobMap; use rustc_trans; @@ -39,6 +40,7 @@ use rustc_trans::back::link; use syntax::ast; use syntax::codemap::CodeMap; use syntax::feature_gate::UnstableFeatures; +use syntax::fold::Folder; use syntax_pos::{BytePos, DUMMY_SP, Pos, Span}; use errors; use errors::emitter::ColorConfig; @@ -72,6 +74,7 @@ pub fn run(input: &str, crate_types: vec![config::CrateTypeDylib], externs: externs.clone(), unstable_features: UnstableFeatures::from_environment(), + lint_cap: Some(::rustc::lint::Level::Allow), actually_rustdoc: true, ..config::basic_options().clone() }; @@ -94,6 +97,7 @@ pub fn run(input: &str, let krate = panictry!(driver::phase_1_parse_input(&driver::CompileController::basic(), &sess, &input)); + let krate = ReplaceBodyWithLoop::new().fold_crate(krate); let driver::ExpansionResult { defs, mut hir_forest, .. } = { phase_2_configure_and_expand( &sess, &cstore, krate, None, "rustdoc-test", None, MakeGlobMap::No, |_| Ok(()) From a2b888675accccedec7601cc3bd67ca028b4757c Mon Sep 17 00:00:00 2001 From: kennytm Date: Sat, 5 Aug 2017 14:38:52 +0800 Subject: [PATCH 2/4] 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. --- .../src/language-features/doc-cfg.md | 42 + src/librustdoc/clean/cfg.rs | 889 ++++++++++++++++++ src/librustdoc/clean/mod.rs | 63 +- src/librustdoc/html/render.rs | 8 + src/librustdoc/html/static/styles/main.css | 1 + src/librustdoc/lib.rs | 1 + src/librustdoc/passes/mod.rs | 6 + src/librustdoc/passes/propagate_doc_cfg.rs | 47 + src/librustdoc/test.rs | 10 +- src/libsyntax/feature_gate.rs | 13 + src/test/compile-fail/feature-gate-doc_cfg.rs | 12 + src/test/rustdoc/doc-cfg.rs | 47 + 12 files changed, 1126 insertions(+), 13 deletions(-) create mode 100644 src/doc/unstable-book/src/language-features/doc-cfg.md create mode 100644 src/librustdoc/clean/cfg.rs create mode 100644 src/librustdoc/passes/propagate_doc_cfg.rs create mode 100644 src/test/compile-fail/feature-gate-doc_cfg.rs create mode 100644 src/test/rustdoc/doc-cfg.rs 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 0000000000000..ddc538e12144a --- /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::(); +/// 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 0000000000000..da8c3a5cf206b --- /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 or the MIT license +// , 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), + /// Negate a configuration requirement, i.e. `not(x)`. + Not(Box), + /// Union of a list of configuration requirements, i.e. `any(...)`. + Any(Vec), + /// Intersection of a list of configuration requirements, i.e. `all(...)`. + All(Vec), +} + +#[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 { + 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 { + 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 {}", 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( + 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, "{}=\"{}\"", Escape(n), Escape(&*v.as_str())) + } else { + write!(fmt, "{}", 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 Unix only." + ); + assert_eq!( + name_value_cfg("target_os", "macos").render_long_html(), + "This is supported on macOS only." + ); + assert_eq!( + name_value_cfg("target_pointer_width", "16").render_long_html(), + "This is supported on 16-bit only." + ); + assert_eq!( + name_value_cfg("target_endian", "little").render_long_html(), + "This is supported on little-endian only." + ); + assert_eq!( + (!word_cfg("windows")).render_long_html(), + "This is supported on non-Windows only." + ); + assert_eq!( + (word_cfg("unix") & word_cfg("windows")).render_long_html(), + "This is supported on Unix and Windows only." + ); + assert_eq!( + (word_cfg("unix") | word_cfg("windows")).render_long_html(), + "This is supported on Unix or Windows only." + ); + assert_eq!( + ( + word_cfg("unix") & word_cfg("windows") & word_cfg("debug_assertions") + ).render_long_html(), + "This is supported on Unix and Windows and debug-assertions enabled \ + only." + ); + assert_eq!( + ( + word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions") + ).render_long_html(), + "This is supported on Unix or Windows or debug-assertions enabled \ + only." + ); + assert_eq!( + ( + !(word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions")) + ).render_long_html(), + "This is supported on 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_long_html(), + "This is supported on Unix and x86-64, or Windows and 64-bit only." + ); + assert_eq!( + (!(word_cfg("unix") & word_cfg("windows"))).render_long_html(), + "This is supported on not (Unix and Windows)." + ); + assert_eq!( + ( + (word_cfg("debug_assertions") | word_cfg("windows")) & word_cfg("unix") + ).render_long_html(), + "This is supported on (debug-assertions enabled or Windows) and Unix \ + only." + ); + } +} \ No newline at end of file diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index a9636c7e2fd73..57e72c3a40bf5 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 { cx.tcx.lookup_stability(def_id).clean(cx) @@ -536,31 +539,67 @@ impl> NestedAttributesExt for I { pub struct Attributes { pub doc_strings: Vec, pub other_attrs: Vec, + pub cfg: Option>, pub span: Option, } 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 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 fc0adef70baa1..95aa8e97dbbac 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{}", text)) } + if let Some(ref cfg) = item.attrs.cfg { + stability.push(format!("
{}
", 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 034c5307fc080..08bf5a10fe9d9 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 64240d26894d0..9264015ed9edf 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 41fd9efe61e01..146629486fabd 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 0000000000000..9e65fff5e2ac6 --- /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 or the MIT license +// , 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>, +} + +impl DocFolder for CfgPropagator { + fn fold_item(&mut self, mut item: Item) -> Option { + 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 8e24a3b587920..fff047c99c034 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 aeb574bc3af3c..668732e6855b9 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 0000000000000..1a77d91801457 --- /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 or the MIT license +// , 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 0000000000000..cfb37912fe757 --- /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 or the MIT license +// , 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 From b4114ebe3a19d7d9bdacf700cc67bd2709eafe5b Mon Sep 17 00:00:00 2001 From: kennytm Date: Sat, 5 Aug 2017 14:39:46 +0800 Subject: [PATCH 3/4] Exposed all platform-specific documentation. --- src/libstd/lib.rs | 1 + src/libstd/os/mod.rs | 42 +++++++++++++++++++------------ src/libstd/sys/mod.rs | 30 ++++++++++++++++++++++ src/libstd/sys/redox/ext/mod.rs | 1 + src/libstd/sys/unix/ext/mod.rs | 1 + src/libstd/sys/unix/mod.rs | 29 ++++++++++----------- src/libstd/sys/windows/ext/mod.rs | 1 + 7 files changed, 75 insertions(+), 30 deletions(-) diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index bd9c9c7478489..cc24b7937c0eb 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -314,6 +314,7 @@ #![feature(untagged_unions)] #![feature(unwind_attributes)] #![feature(vec_push_all)] +#![feature(doc_cfg)] #![cfg_attr(test, feature(update_panic_count))] #![default_lib_allocator] diff --git a/src/libstd/os/mod.rs b/src/libstd/os/mod.rs index e45af86705582..72eed549f62a0 100644 --- a/src/libstd/os/mod.rs +++ b/src/libstd/os/mod.rs @@ -13,26 +13,36 @@ #![stable(feature = "os", since = "1.0.0")] #![allow(missing_docs, bad_style, missing_debug_implementations)] -#[cfg(any(target_os = "redox", unix))] +#[cfg(all(not(dox), any(target_os = "redox", unix)))] #[stable(feature = "rust1", since = "1.0.0")] pub use sys::ext as unix; -#[cfg(windows)] +#[cfg(all(not(dox), windows))] #[stable(feature = "rust1", since = "1.0.0")] pub use sys::ext as windows; -#[cfg(target_os = "android")] pub mod android; -#[cfg(target_os = "bitrig")] pub mod bitrig; -#[cfg(target_os = "dragonfly")] pub mod dragonfly; -#[cfg(target_os = "freebsd")] pub mod freebsd; -#[cfg(target_os = "haiku")] pub mod haiku; -#[cfg(target_os = "ios")] pub mod ios; -#[cfg(target_os = "linux")] pub mod linux; -#[cfg(target_os = "macos")] pub mod macos; -#[cfg(target_os = "nacl")] pub mod nacl; -#[cfg(target_os = "netbsd")] pub mod netbsd; -#[cfg(target_os = "openbsd")] pub mod openbsd; -#[cfg(target_os = "solaris")] pub mod solaris; -#[cfg(target_os = "emscripten")] pub mod emscripten; -#[cfg(target_os = "fuchsia")] pub mod fuchsia; +#[cfg(dox)] +#[stable(feature = "rust1", since = "1.0.0")] +pub use sys::unix_ext as unix; +#[cfg(dox)] +#[stable(feature = "rust1", since = "1.0.0")] +pub use sys::windows_ext as windows; + +#[cfg(any(dox, target_os = "linux"))] +#[doc(cfg(target_os = "linux"))] +pub mod linux; + +#[cfg(all(not(dox), target_os = "android"))] pub mod android; +#[cfg(all(not(dox), target_os = "bitrig"))] pub mod bitrig; +#[cfg(all(not(dox), target_os = "dragonfly"))] pub mod dragonfly; +#[cfg(all(not(dox), target_os = "freebsd"))] pub mod freebsd; +#[cfg(all(not(dox), target_os = "haiku"))] pub mod haiku; +#[cfg(all(not(dox), target_os = "ios"))] pub mod ios; +#[cfg(all(not(dox), target_os = "macos"))] pub mod macos; +#[cfg(all(not(dox), target_os = "nacl"))] pub mod nacl; +#[cfg(all(not(dox), target_os = "netbsd"))] pub mod netbsd; +#[cfg(all(not(dox), target_os = "openbsd"))] pub mod openbsd; +#[cfg(all(not(dox), target_os = "solaris"))] pub mod solaris; +#[cfg(all(not(dox), target_os = "emscripten"))] pub mod emscripten; +#[cfg(all(not(dox), target_os = "fuchsia"))] pub mod fuchsia; pub mod raw; diff --git a/src/libstd/sys/mod.rs b/src/libstd/sys/mod.rs index ef4dc365dbef8..d91c2073a23a8 100644 --- a/src/libstd/sys/mod.rs +++ b/src/libstd/sys/mod.rs @@ -45,3 +45,33 @@ mod imp; #[cfg(target_os = "redox")] #[path = "redox/mod.rs"] mod imp; + + +// Import essential modules from both platforms when documenting. + +#[cfg(all(dox, not(unix)))] +use os::linux as platform; + +#[cfg(all(dox, not(any(unix, target_os = "redox"))))] +#[path = "unix/ext/mod.rs"] +pub mod unix_ext; + +#[cfg(all(dox, any(unix, target_os = "redox")))] +pub use self::ext as unix_ext; + + +#[cfg(all(dox, not(windows)))] +#[macro_use] +#[path = "windows/compat.rs"] +mod compat; + +#[cfg(all(dox, not(windows)))] +#[path = "windows/c.rs"] +mod c; + +#[cfg(all(dox, not(windows)))] +#[path = "windows/ext/mod.rs"] +pub mod windows_ext; + +#[cfg(all(dox, windows))] +pub use self::ext as windows_ext; diff --git a/src/libstd/sys/redox/ext/mod.rs b/src/libstd/sys/redox/ext/mod.rs index 0c1bf9e955760..259cda5bcb3eb 100644 --- a/src/libstd/sys/redox/ext/mod.rs +++ b/src/libstd/sys/redox/ext/mod.rs @@ -28,6 +28,7 @@ //! ``` #![stable(feature = "rust1", since = "1.0.0")] +#![doc(cfg(target_os = "redox"))] pub mod ffi; pub mod fs; diff --git a/src/libstd/sys/unix/ext/mod.rs b/src/libstd/sys/unix/ext/mod.rs index 1be9f11b92c73..67fe46cc9c7a2 100644 --- a/src/libstd/sys/unix/ext/mod.rs +++ b/src/libstd/sys/unix/ext/mod.rs @@ -28,6 +28,7 @@ //! ``` #![stable(feature = "rust1", since = "1.0.0")] +#![doc(cfg(unix))] pub mod io; pub mod ffi; diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index 46e5acdf3d22b..4393aedf162a3 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -13,20 +13,21 @@ use io::{self, ErrorKind}; use libc; -#[cfg(target_os = "android")] pub use os::android as platform; -#[cfg(target_os = "bitrig")] pub use os::bitrig as platform; -#[cfg(target_os = "dragonfly")] pub use os::dragonfly as platform; -#[cfg(target_os = "freebsd")] pub use os::freebsd as platform; -#[cfg(target_os = "haiku")] pub use os::haiku as platform; -#[cfg(target_os = "ios")] pub use os::ios as platform; -#[cfg(target_os = "linux")] pub use os::linux as platform; -#[cfg(target_os = "macos")] pub use os::macos as platform; -#[cfg(target_os = "nacl")] pub use os::nacl as platform; -#[cfg(target_os = "netbsd")] pub use os::netbsd as platform; -#[cfg(target_os = "openbsd")] pub use os::openbsd as platform; -#[cfg(target_os = "solaris")] pub use os::solaris as platform; -#[cfg(target_os = "emscripten")] pub use os::emscripten as platform; -#[cfg(target_os = "fuchsia")] pub use os::fuchsia as platform; +#[cfg(any(dox, target_os = "linux"))] pub use os::linux as platform; + +#[cfg(all(not(dox), target_os = "android"))] pub use os::android as platform; +#[cfg(all(not(dox), target_os = "bitrig"))] pub use os::bitrig as platform; +#[cfg(all(not(dox), target_os = "dragonfly"))] pub use os::dragonfly as platform; +#[cfg(all(not(dox), target_os = "freebsd"))] pub use os::freebsd as platform; +#[cfg(all(not(dox), target_os = "haiku"))] pub use os::haiku as platform; +#[cfg(all(not(dox), target_os = "ios"))] pub use os::ios as platform; +#[cfg(all(not(dox), target_os = "macos"))] pub use os::macos as platform; +#[cfg(all(not(dox), target_os = "nacl"))] pub use os::nacl as platform; +#[cfg(all(not(dox), target_os = "netbsd"))] pub use os::netbsd as platform; +#[cfg(all(not(dox), target_os = "openbsd"))] pub use os::openbsd as platform; +#[cfg(all(not(dox), target_os = "solaris"))] pub use os::solaris as platform; +#[cfg(all(not(dox), target_os = "emscripten"))] pub use os::emscripten as platform; +#[cfg(all(not(dox), target_os = "fuchsia"))] pub use os::fuchsia as platform; #[macro_use] pub mod weak; diff --git a/src/libstd/sys/windows/ext/mod.rs b/src/libstd/sys/windows/ext/mod.rs index 11b1337a8aec0..4b458d293bce2 100644 --- a/src/libstd/sys/windows/ext/mod.rs +++ b/src/libstd/sys/windows/ext/mod.rs @@ -17,6 +17,7 @@ //! platform-agnostic idioms would not normally support. #![stable(feature = "rust1", since = "1.0.0")] +#![doc(cfg(windows))] pub mod ffi; pub mod fs; From 3093bb85f94e6f3c4707674c8b70c28ecfbf3bf9 Mon Sep 17 00:00:00 2001 From: kennytm Date: Fri, 11 Aug 2017 09:01:46 +0800 Subject: [PATCH 4/4] Fix error during cross-platform documentation. --- src/libstd/sys/unix/ext/net.rs | 11 +++++++++++ src/libstd/sys/windows/c.rs | 12 ++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/libstd/sys/unix/ext/net.rs b/src/libstd/sys/unix/ext/net.rs index 94b87a6bff490..1644ded5e5ceb 100644 --- a/src/libstd/sys/unix/ext/net.rs +++ b/src/libstd/sys/unix/ext/net.rs @@ -12,8 +12,19 @@ //! Unix-specific networking functionality +#[cfg(unix)] use libc; +// FIXME(#43348): Make libc adapt #[doc(cfg(...))] so we don't need these fake definitions here? +#[cfg(not(unix))] +mod libc { + pub use libc::c_int; + pub type socklen_t = u32; + pub struct sockaddr; + #[derive(Clone)] + pub struct sockaddr_un; +} + use ascii; use ffi::OsStr; use fmt; diff --git a/src/libstd/sys/windows/c.rs b/src/libstd/sys/windows/c.rs index 4785cefd6b4b7..ba54ca6ea1828 100644 --- a/src/libstd/sys/windows/c.rs +++ b/src/libstd/sys/windows/c.rs @@ -301,7 +301,7 @@ pub const PIPE_READMODE_BYTE: DWORD = 0x00000000; pub const FD_SETSIZE: usize = 64; #[repr(C)] -#[cfg(target_arch = "x86")] +#[cfg(not(target_pointer_width = "64"))] pub struct WSADATA { pub wVersion: WORD, pub wHighVersion: WORD, @@ -312,7 +312,7 @@ pub struct WSADATA { pub lpVendorInfo: *mut u8, } #[repr(C)] -#[cfg(target_arch = "x86_64")] +#[cfg(target_pointer_width = "64")] pub struct WSADATA { pub wVersion: WORD, pub wHighVersion: WORD, @@ -768,6 +768,14 @@ pub struct FLOATING_SAVE_AREA { _Dummy: [u8; 512] // FIXME: Fill this out } +// FIXME(#43348): This structure is used for backtrace only, and a fake +// definition is provided here only to allow rustdoc to pass type-check. This +// will not appear in the final documentation. This should be also defined for +// other architectures supported by Windows such as ARM, and for historical +// interest, maybe MIPS and PowerPC as well. +#[cfg(all(dox, not(any(target_arch = "x86_64", target_arch = "x86"))))] +pub enum CONTEXT {} + #[repr(C)] pub struct SOCKADDR_STORAGE_LH { pub ss_family: ADDRESS_FAMILY,