// 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 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, Eq, Hash)] 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, true).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 on = if self.should_use_with_in_description() { "with" } else { "on" }; let mut msg = format!("This is supported {} {}", on, Html(self, false)); 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, } } } fn should_use_with_in_description(&self) -> bool { match *self { Cfg::Cfg(ref name, _) if name == &"target_feature" => 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 } } /// Pretty-print wrapper for a `Cfg`. Also indicates whether the "short-form" rendering should be /// used. struct Html<'a>(&'a Cfg, bool); 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, self.1))?; } Ok(()) } ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Html(simple, self.1)), ref c => write!(fmt, "not ({})", Html(c, self.1)), }, 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, self.1))?; } 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, self.1))?; } 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", "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), ("target_feature", Some(feat)) => if self.1 { return write!(fmt, "{}", feat); } else { return write!(fmt, "target feature {}", feat); }, _ => "", }; 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; use syntax::with_globals; 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))) } fn dummy_meta_item_word(name: &str) -> MetaItem { MetaItem { ident: Path::from_ident(Ident::from_str(name)), node: MetaItemKind::Word, span: DUMMY_SP, } } macro_rules! dummy_meta_item_list { ($name:ident, [$($list:ident),* $(,)*]) => { MetaItem { ident: Path::from_ident(Ident::from_str(stringify!($name))), node: MetaItemKind::List(vec![ $( dummy_spanned(NestedMetaItemKind::MetaItem( dummy_meta_item_word(stringify!($list)), )), )* ]), span: DUMMY_SP, } }; ($name:ident, [$($list:expr),* $(,)*]) => { MetaItem { ident: Path::from_ident(Ident::from_str(stringify!($name))), node: MetaItemKind::List(vec![ $( dummy_spanned(NestedMetaItemKind::MetaItem($list)), )* ]), span: DUMMY_SP, } }; } #[test] fn test_cfg_not() { with_globals(|| { 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() { with_globals(|| { 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() { with_globals(|| { 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() { with_globals(|| { let mi = dummy_meta_item_word("all"); assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all"))); let mi = MetaItem { ident: Path::from_ident(Ident::from_str("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 = dummy_meta_item_list!(all, [a, b]); assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b"))); let mi = dummy_meta_item_list!(any, [a, b]); assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") | word_cfg("b"))); let mi = dummy_meta_item_list!(not, [a]); assert_eq!(Cfg::parse(&mi), Ok(!word_cfg("a"))); let mi = dummy_meta_item_list!(not, [ dummy_meta_item_list!(any, [ dummy_meta_item_word("a"), dummy_meta_item_list!(all, [b, c]), ]), ]); assert_eq!(Cfg::parse(&mi), Ok(!(word_cfg("a") | (word_cfg("b") & word_cfg("c"))))); let mi = dummy_meta_item_list!(all, [a, b, c]); assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b") & word_cfg("c"))); }) } #[test] fn test_parse_err() { with_globals(|| { let mi = MetaItem { ident: Path::from_ident(Ident::from_str("foo")), node: MetaItemKind::NameValue(dummy_spanned(LitKind::Bool(false))), span: DUMMY_SP, }; assert!(Cfg::parse(&mi).is_err()); let mi = dummy_meta_item_list!(not, [a, b]); assert!(Cfg::parse(&mi).is_err()); let mi = dummy_meta_item_list!(not, []); assert!(Cfg::parse(&mi).is_err()); let mi = dummy_meta_item_list!(foo, []); assert!(Cfg::parse(&mi).is_err()); let mi = dummy_meta_item_list!(all, [ dummy_meta_item_list!(foo, []), dummy_meta_item_word("b"), ]); assert!(Cfg::parse(&mi).is_err()); let mi = dummy_meta_item_list!(any, [ dummy_meta_item_word("a"), dummy_meta_item_list!(foo, []), ]); assert!(Cfg::parse(&mi).is_err()); let mi = dummy_meta_item_list!(not, [ dummy_meta_item_list!(foo, []), ]); assert!(Cfg::parse(&mi).is_err()); }) } #[test] fn test_render_short_html() { with_globals(|| { 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" ); assert_eq!( name_value_cfg("target_feature", "sse2").render_short_html(), "sse2" ); assert_eq!( ( name_value_cfg("target_arch", "x86_64") & name_value_cfg("target_feature", "sse2") ).render_short_html(), "x86-64 and sse2" ); }) } #[test] fn test_render_long_html() { with_globals(|| { 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." ); assert_eq!( name_value_cfg("target_feature", "sse2").render_long_html(), "This is supported with target feature sse2 only." ); assert_eq!( ( name_value_cfg("target_arch", "x86_64") & name_value_cfg("target_feature", "sse2") ).render_long_html(), "This is supported on x86-64 and target feature \ sse2 only." ); }) } }