1 //! The representation of a `#[doc(cfg(...))]` attribute.
3 // FIXME: Once the portability lint RFC is implemented (see tracking issue #41619),
4 // switch to use those structures instead.
7 use std::fmt::{self, Write};
10 use syntax::symbol::Symbol;
11 use syntax::ast::{MetaItem, MetaItemKind, NestedMetaItem, LitKind};
12 use syntax::parse::ParseSess;
13 use syntax::feature_gate::Features;
17 use crate::html::escape::Escape;
19 #[derive(Clone, RustcEncodable, RustcDecodable, Debug, PartialEq, Eq, Hash)]
21 /// Accepts all configurations.
23 /// Denies all configurations.
25 /// A generic configuration option, e.g., `test` or `target_os = "linux"`.
26 Cfg(Symbol, Option<Symbol>),
27 /// Negates a configuration requirement, i.e., `not(x)`.
29 /// Union of a list of configuration requirements, i.e., `any(...)`.
31 /// Intersection of a list of configuration requirements, i.e., `all(...)`.
35 #[derive(PartialEq, Debug)]
36 pub struct InvalidCfgError {
37 pub msg: &'static str,
42 /// Parses a `NestedMetaItem` into a `Cfg`.
43 fn parse_nested(nested_cfg: &NestedMetaItem) -> Result<Cfg, InvalidCfgError> {
45 NestedMetaItem::MetaItem(ref cfg) => Cfg::parse(cfg),
46 NestedMetaItem::Literal(ref lit) => Err(InvalidCfgError {
47 msg: "unexpected literal",
53 /// Parses a `MetaItem` into a `Cfg`.
55 /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or
56 /// `target_os = "redox"`.
58 /// If the content is not properly formatted, it will return an error indicating what and where
60 pub fn parse(cfg: &MetaItem) -> Result<Cfg, InvalidCfgError> {
61 let name = match cfg.ident() {
62 Some(ident) => ident.name,
63 None => return Err(InvalidCfgError {
64 msg: "expected a single identifier",
69 MetaItemKind::Word => Ok(Cfg::Cfg(name, None)),
70 MetaItemKind::NameValue(ref lit) => match lit.node {
71 LitKind::Str(value, _) => Ok(Cfg::Cfg(name, Some(value))),
72 _ => Err(InvalidCfgError {
73 // FIXME: if the main #[cfg] syntax decided to support non-string literals,
74 // this should be changed as well.
75 msg: "value of cfg option should be a string literal",
79 MetaItemKind::List(ref items) => {
80 let mut sub_cfgs = items.iter().map(Cfg::parse_nested);
81 match &*name.as_str() {
82 "all" => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)),
83 "any" => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)),
84 "not" => if sub_cfgs.len() == 1 {
85 Ok(!sub_cfgs.next().unwrap()?)
88 msg: "expected 1 cfg-pattern",
92 _ => Err(InvalidCfgError {
93 msg: "invalid predicate",
101 /// Checks whether the given configuration can be matched in the current session.
103 /// Equivalent to `attr::cfg_matches`.
104 // FIXME: Actually make use of `features`.
105 pub fn matches(&self, parse_sess: &ParseSess, features: Option<&Features>) -> bool {
109 Cfg::Not(ref child) => !child.matches(parse_sess, features),
110 Cfg::All(ref sub_cfgs) => {
111 sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(parse_sess, features))
113 Cfg::Any(ref sub_cfgs) => {
114 sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(parse_sess, features))
116 Cfg::Cfg(name, value) => parse_sess.config.contains(&(name, value)),
120 /// Whether the configuration consists of just `Cfg` or `Not`.
121 fn is_simple(&self) -> bool {
123 Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
124 Cfg::All(..) | Cfg::Any(..) => false,
128 /// Whether the configuration consists of just `Cfg`, `Not` or `All`.
129 fn is_all(&self) -> bool {
131 Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
132 Cfg::Any(..) => false,
136 /// Renders the configuration for human display, as a short HTML description.
137 pub(crate) fn render_short_html(&self) -> String {
138 let mut msg = Html(self, true).to_string();
139 if self.should_capitalize_first_letter() {
140 if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
141 msg[i .. i+1].make_ascii_uppercase();
147 /// Renders the configuration for long display, as a long HTML description.
148 pub(crate) fn render_long_html(&self) -> String {
149 let on = if self.should_use_with_in_description() {
155 let mut msg = format!("This is supported {} <strong>{}</strong>", on, Html(self, false));
156 if self.should_append_only_to_description() {
157 msg.push_str(" only");
163 fn should_capitalize_first_letter(&self) -> bool {
165 Cfg::False | Cfg::True | Cfg::Not(..) => true,
166 Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
167 sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
169 Cfg::Cfg(name, _) => match &*name.as_str() {
170 "debug_assertions" | "target_endian" => true,
176 fn should_append_only_to_description(&self) -> bool {
178 Cfg::False | Cfg::True => false,
179 Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
180 Cfg::Not(ref child) => match **child {
181 Cfg::Cfg(..) => true,
187 fn should_use_with_in_description(&self) -> bool {
189 Cfg::Cfg(ref name, _) if name == &"target_feature" => true,
195 impl ops::Not for Cfg {
197 fn not(self) -> Cfg {
199 Cfg::False => Cfg::True,
200 Cfg::True => Cfg::False,
201 Cfg::Not(cfg) => *cfg,
202 s => Cfg::Not(Box::new(s)),
207 impl ops::BitAndAssign for Cfg {
208 fn bitand_assign(&mut self, other: Cfg) {
209 match (self, other) {
210 (&mut Cfg::False, _) | (_, Cfg::True) => {},
211 (s, Cfg::False) => *s = Cfg::False,
212 (s @ &mut Cfg::True, b) => *s = b,
213 (&mut Cfg::All(ref mut a), Cfg::All(ref mut b)) => a.append(b),
214 (&mut Cfg::All(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
215 (s, Cfg::All(mut a)) => {
216 let b = mem::replace(s, Cfg::True);
221 let a = mem::replace(s, Cfg::True);
222 *s = Cfg::All(vec![a, b]);
228 impl ops::BitAnd for Cfg {
230 fn bitand(mut self, other: Cfg) -> Cfg {
236 impl ops::BitOrAssign for Cfg {
237 fn bitor_assign(&mut self, other: Cfg) {
238 match (self, other) {
239 (&mut Cfg::True, _) | (_, Cfg::False) => {},
240 (s, Cfg::True) => *s = Cfg::True,
241 (s @ &mut Cfg::False, b) => *s = b,
242 (&mut Cfg::Any(ref mut a), Cfg::Any(ref mut b)) => a.append(b),
243 (&mut Cfg::Any(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
244 (s, Cfg::Any(mut a)) => {
245 let b = mem::replace(s, Cfg::True);
250 let a = mem::replace(s, Cfg::True);
251 *s = Cfg::Any(vec![a, b]);
257 impl ops::BitOr for Cfg {
259 fn bitor(mut self, other: Cfg) -> Cfg {
265 /// Pretty-print wrapper for a `Cfg`. Also indicates whether the "short-form" rendering should be
267 struct Html<'a>(&'a Cfg, bool);
269 fn write_with_opt_paren<T: fmt::Display>(
270 fmt: &mut fmt::Formatter<'_>,
275 fmt.write_char('(')?;
279 fmt.write_char(')')?;
285 impl<'a> fmt::Display for Html<'a> {
286 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
288 Cfg::Not(ref child) => match **child {
289 Cfg::Any(ref sub_cfgs) => {
290 let separator = if sub_cfgs.iter().all(Cfg::is_simple) {
295 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
296 fmt.write_str(if i == 0 { "neither " } else { separator })?;
297 write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
301 ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Html(simple, self.1)),
302 ref c => write!(fmt, "not ({})", Html(c, self.1)),
305 Cfg::Any(ref sub_cfgs) => {
306 let separator = if sub_cfgs.iter().all(Cfg::is_simple) {
311 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
313 fmt.write_str(separator)?;
315 write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
320 Cfg::All(ref sub_cfgs) => {
321 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
323 fmt.write_str(" and ")?;
325 write_with_opt_paren(fmt, !sub_cfg.is_simple(), Html(sub_cfg, self.1))?;
330 Cfg::True => fmt.write_str("everywhere"),
331 Cfg::False => fmt.write_str("nowhere"),
333 Cfg::Cfg(name, value) => {
334 let n = &*name.as_str();
335 let human_readable = match (n, value) {
336 ("unix", None) => "Unix",
337 ("windows", None) => "Windows",
338 ("debug_assertions", None) => "debug-assertions enabled",
339 ("target_os", Some(os)) => match &*os.as_str() {
340 "android" => "Android",
341 "bitrig" => "Bitrig",
342 "dragonfly" => "DragonFly BSD",
343 "emscripten" => "Emscripten",
344 "freebsd" => "FreeBSD",
345 "fuchsia" => "Fuchsia",
351 "netbsd" => "NetBSD",
352 "openbsd" => "OpenBSD",
354 "solaris" => "Solaris",
355 "windows" => "Windows",
358 ("target_arch", Some(arch)) => match &*arch.as_str() {
359 "aarch64" => "AArch64",
363 "mips64" => "MIPS-64",
364 "msp430" => "MSP430",
365 "powerpc" => "PowerPC",
366 "powerpc64" => "PowerPC-64",
368 "sparc64" => "SPARC64",
369 "wasm32" => "WebAssembly",
371 "x86_64" => "x86-64",
374 ("target_vendor", Some(vendor)) => match &*vendor.as_str() {
377 "rumprun" => "Rumprun",
379 "fortanix" => "Fortanix",
382 ("target_env", Some(env)) => match &*env.as_str() {
386 "newlib" => "Newlib",
387 "uclibc" => "uClibc",
391 ("target_endian", Some(endian)) => return write!(fmt, "{}-endian", endian),
392 ("target_pointer_width", Some(bits)) => return write!(fmt, "{}-bit", bits),
393 ("target_feature", Some(feat)) =>
395 return write!(fmt, "<code>{}</code>", feat);
397 return write!(fmt, "target feature <code>{}</code>", feat);
401 if !human_readable.is_empty() {
402 fmt.write_str(human_readable)
403 } else if let Some(v) = value {
404 write!(fmt, "<code>{}=\"{}\"</code>", Escape(n), Escape(&*v.as_str()))
406 write!(fmt, "<code>{}</code>", Escape(n))
417 use syntax_pos::DUMMY_SP;
419 use syntax::symbol::Symbol;
420 use syntax::with_globals;
422 fn word_cfg(s: &str) -> Cfg {
423 Cfg::Cfg(Symbol::intern(s), None)
426 fn name_value_cfg(name: &str, value: &str) -> Cfg {
427 Cfg::Cfg(Symbol::intern(name), Some(Symbol::intern(value)))
430 fn dummy_meta_item_word(name: &str) -> MetaItem {
432 path: Path::from_ident(Ident::from_str(name)),
433 node: MetaItemKind::Word,
438 macro_rules! dummy_meta_item_list {
439 ($name:ident, [$($list:ident),* $(,)?]) => {
441 path: Path::from_ident(Ident::from_str(stringify!($name))),
442 node: MetaItemKind::List(vec![
444 NestedMetaItem::MetaItem(
445 dummy_meta_item_word(stringify!($list)),
453 ($name:ident, [$($list:expr),* $(,)?]) => {
455 path: Path::from_ident(Ident::from_str(stringify!($name))),
456 node: MetaItemKind::List(vec![
458 NestedMetaItem::MetaItem($list),
469 assert_eq!(!Cfg::False, Cfg::True);
470 assert_eq!(!Cfg::True, Cfg::False);
471 assert_eq!(!word_cfg("test"), Cfg::Not(Box::new(word_cfg("test"))));
473 !Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
474 Cfg::Not(Box::new(Cfg::All(vec![word_cfg("a"), word_cfg("b")])))
477 !Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
478 Cfg::Not(Box::new(Cfg::Any(vec![word_cfg("a"), word_cfg("b")])))
480 assert_eq!(!Cfg::Not(Box::new(word_cfg("test"))), word_cfg("test"));
487 let mut x = Cfg::False;
489 assert_eq!(x, Cfg::False);
491 x = word_cfg("test");
493 assert_eq!(x, Cfg::False);
495 x = word_cfg("test2");
497 assert_eq!(x, word_cfg("test2"));
500 x &= word_cfg("test3");
501 assert_eq!(x, word_cfg("test3"));
503 x &= word_cfg("test4");
504 assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4")]));
506 x &= word_cfg("test5");
507 assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
509 x &= Cfg::All(vec![word_cfg("test6"), word_cfg("test7")]);
510 assert_eq!(x, Cfg::All(vec![
518 let mut y = Cfg::Any(vec![word_cfg("a"), word_cfg("b")]);
520 assert_eq!(y, Cfg::All(vec![
526 Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
530 word_cfg("a") & word_cfg("b") & word_cfg("c"),
531 Cfg::All(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
539 let mut x = Cfg::True;
541 assert_eq!(x, Cfg::True);
543 x = word_cfg("test");
545 assert_eq!(x, Cfg::True);
547 x = word_cfg("test2");
549 assert_eq!(x, word_cfg("test2"));
552 x |= word_cfg("test3");
553 assert_eq!(x, word_cfg("test3"));
555 x |= word_cfg("test4");
556 assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4")]));
558 x |= word_cfg("test5");
559 assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
561 x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("test7")]);
562 assert_eq!(x, Cfg::Any(vec![
570 let mut y = Cfg::All(vec![word_cfg("a"), word_cfg("b")]);
572 assert_eq!(y, Cfg::Any(vec![
578 Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
582 word_cfg("a") | word_cfg("b") | word_cfg("c"),
583 Cfg::Any(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
591 let mi = dummy_meta_item_word("all");
592 assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all")));
594 let node = LitKind::Str(Symbol::intern("done"), StrStyle::Cooked);
595 let (token, suffix) = node.lit_token();
597 path: Path::from_ident(Ident::from_str("all")),
598 node: MetaItemKind::NameValue(Lit { node, token, suffix, span: DUMMY_SP }),
601 assert_eq!(Cfg::parse(&mi), Ok(name_value_cfg("all", "done")));
603 let mi = dummy_meta_item_list!(all, [a, b]);
604 assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b")));
606 let mi = dummy_meta_item_list!(any, [a, b]);
607 assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") | word_cfg("b")));
609 let mi = dummy_meta_item_list!(not, [a]);
610 assert_eq!(Cfg::parse(&mi), Ok(!word_cfg("a")));
612 let mi = dummy_meta_item_list!(not, [
613 dummy_meta_item_list!(any, [
614 dummy_meta_item_word("a"),
615 dummy_meta_item_list!(all, [b, c]),
618 assert_eq!(Cfg::parse(&mi), Ok(!(word_cfg("a") | (word_cfg("b") & word_cfg("c")))));
620 let mi = dummy_meta_item_list!(all, [a, b, c]);
621 assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b") & word_cfg("c")));
626 fn test_parse_err() {
628 let node = LitKind::Bool(false);
629 let (token, suffix) = node.lit_token();
631 path: Path::from_ident(Ident::from_str("foo")),
632 node: MetaItemKind::NameValue(Lit { node, token, suffix, span: DUMMY_SP }),
635 assert!(Cfg::parse(&mi).is_err());
637 let mi = dummy_meta_item_list!(not, [a, b]);
638 assert!(Cfg::parse(&mi).is_err());
640 let mi = dummy_meta_item_list!(not, []);
641 assert!(Cfg::parse(&mi).is_err());
643 let mi = dummy_meta_item_list!(foo, []);
644 assert!(Cfg::parse(&mi).is_err());
646 let mi = dummy_meta_item_list!(all, [
647 dummy_meta_item_list!(foo, []),
648 dummy_meta_item_word("b"),
650 assert!(Cfg::parse(&mi).is_err());
652 let mi = dummy_meta_item_list!(any, [
653 dummy_meta_item_word("a"),
654 dummy_meta_item_list!(foo, []),
656 assert!(Cfg::parse(&mi).is_err());
658 let mi = dummy_meta_item_list!(not, [
659 dummy_meta_item_list!(foo, []),
661 assert!(Cfg::parse(&mi).is_err());
666 fn test_render_short_html() {
669 word_cfg("unix").render_short_html(),
673 name_value_cfg("target_os", "macos").render_short_html(),
677 name_value_cfg("target_pointer_width", "16").render_short_html(),
681 name_value_cfg("target_endian", "little").render_short_html(),
685 (!word_cfg("windows")).render_short_html(),
689 (word_cfg("unix") & word_cfg("windows")).render_short_html(),
693 (word_cfg("unix") | word_cfg("windows")).render_short_html(),
698 word_cfg("unix") & word_cfg("windows") & word_cfg("debug_assertions")
699 ).render_short_html(),
700 "Unix and Windows and debug-assertions enabled"
704 word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions")
705 ).render_short_html(),
706 "Unix or Windows or debug-assertions enabled"
710 !(word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions"))
711 ).render_short_html(),
712 "Neither Unix nor Windows nor debug-assertions enabled"
716 (word_cfg("unix") & name_value_cfg("target_arch", "x86_64")) |
717 (word_cfg("windows") & name_value_cfg("target_pointer_width", "64"))
718 ).render_short_html(),
719 "Unix and x86-64, or Windows and 64-bit"
722 (!(word_cfg("unix") & word_cfg("windows"))).render_short_html(),
723 "Not (Unix and Windows)"
727 (word_cfg("debug_assertions") | word_cfg("windows")) & word_cfg("unix")
728 ).render_short_html(),
729 "(Debug-assertions enabled or Windows) and Unix"
732 name_value_cfg("target_feature", "sse2").render_short_html(),
737 name_value_cfg("target_arch", "x86_64") &
738 name_value_cfg("target_feature", "sse2")
739 ).render_short_html(),
740 "x86-64 and <code>sse2</code>"
746 fn test_render_long_html() {
749 word_cfg("unix").render_long_html(),
750 "This is supported on <strong>Unix</strong> only."
753 name_value_cfg("target_os", "macos").render_long_html(),
754 "This is supported on <strong>macOS</strong> only."
757 name_value_cfg("target_pointer_width", "16").render_long_html(),
758 "This is supported on <strong>16-bit</strong> only."
761 name_value_cfg("target_endian", "little").render_long_html(),
762 "This is supported on <strong>little-endian</strong> only."
765 (!word_cfg("windows")).render_long_html(),
766 "This is supported on <strong>non-Windows</strong> only."
769 (word_cfg("unix") & word_cfg("windows")).render_long_html(),
770 "This is supported on <strong>Unix and Windows</strong> only."
773 (word_cfg("unix") | word_cfg("windows")).render_long_html(),
774 "This is supported on <strong>Unix or Windows</strong> only."
778 word_cfg("unix") & word_cfg("windows") & word_cfg("debug_assertions")
779 ).render_long_html(),
780 "This is supported on <strong>Unix and Windows and debug-assertions enabled\
785 word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions")
786 ).render_long_html(),
787 "This is supported on <strong>Unix or Windows or debug-assertions enabled\
792 !(word_cfg("unix") | word_cfg("windows") | word_cfg("debug_assertions"))
793 ).render_long_html(),
794 "This is supported on <strong>neither Unix nor Windows nor debug-assertions \
799 (word_cfg("unix") & name_value_cfg("target_arch", "x86_64")) |
800 (word_cfg("windows") & name_value_cfg("target_pointer_width", "64"))
801 ).render_long_html(),
802 "This is supported on <strong>Unix and x86-64, or Windows and 64-bit</strong> \
806 (!(word_cfg("unix") & word_cfg("windows"))).render_long_html(),
807 "This is supported on <strong>not (Unix and Windows)</strong>."
811 (word_cfg("debug_assertions") | word_cfg("windows")) & word_cfg("unix")
812 ).render_long_html(),
813 "This is supported on <strong>(debug-assertions enabled or Windows) and Unix\
817 name_value_cfg("target_feature", "sse2").render_long_html(),
818 "This is supported with <strong>target feature <code>sse2</code></strong> only."
822 name_value_cfg("target_arch", "x86_64") &
823 name_value_cfg("target_feature", "sse2")
824 ).render_long_html(),
825 "This is supported on <strong>x86-64 and target feature \
826 <code>sse2</code></strong> only."