]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/clean/cfg.rs
Add test-examples for Cfg::simplify_with
[rust.git] / src / librustdoc / clean / cfg.rs
1 //! The representation of a `#[doc(cfg(...))]` attribute.
2
3 // FIXME: Once the portability lint RFC is implemented (see tracking issue #41619),
4 // switch to use those structures instead.
5
6 use std::fmt::{self, Write};
7 use std::mem;
8 use std::ops;
9
10 use rustc_ast::{LitKind, MetaItem, MetaItemKind, NestedMetaItem};
11 use rustc_feature::Features;
12 use rustc_session::parse::ParseSess;
13 use rustc_span::symbol::{sym, Symbol};
14
15 use rustc_span::Span;
16
17 use crate::html::escape::Escape;
18
19 #[cfg(test)]
20 mod tests;
21
22 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
23 pub enum Cfg {
24     /// Accepts all configurations.
25     True,
26     /// Denies all configurations.
27     False,
28     /// A generic configuration option, e.g., `test` or `target_os = "linux"`.
29     Cfg(Symbol, Option<Symbol>),
30     /// Negates a configuration requirement, i.e., `not(x)`.
31     Not(Box<Cfg>),
32     /// Union of a list of configuration requirements, i.e., `any(...)`.
33     Any(Vec<Cfg>),
34     /// Intersection of a list of configuration requirements, i.e., `all(...)`.
35     All(Vec<Cfg>),
36 }
37
38 #[derive(PartialEq, Debug)]
39 pub struct InvalidCfgError {
40     pub msg: &'static str,
41     pub span: Span,
42 }
43
44 impl Cfg {
45     /// Parses a `NestedMetaItem` into a `Cfg`.
46     fn parse_nested(nested_cfg: &NestedMetaItem) -> Result<Cfg, InvalidCfgError> {
47         match nested_cfg {
48             NestedMetaItem::MetaItem(ref cfg) => Cfg::parse(cfg),
49             NestedMetaItem::Literal(ref lit) => {
50                 Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
51             }
52         }
53     }
54
55     /// Parses a `MetaItem` into a `Cfg`.
56     ///
57     /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or
58     /// `target_os = "redox"`.
59     ///
60     /// If the content is not properly formatted, it will return an error indicating what and where
61     /// the error is.
62     pub fn parse(cfg: &MetaItem) -> Result<Cfg, InvalidCfgError> {
63         let name = match cfg.ident() {
64             Some(ident) => ident.name,
65             None => {
66                 return Err(InvalidCfgError {
67                     msg: "expected a single identifier",
68                     span: cfg.span,
69                 });
70             }
71         };
72         match cfg.kind {
73             MetaItemKind::Word => Ok(Cfg::Cfg(name, None)),
74             MetaItemKind::NameValue(ref lit) => match lit.kind {
75                 LitKind::Str(value, _) => Ok(Cfg::Cfg(name, Some(value))),
76                 _ => Err(InvalidCfgError {
77                     // FIXME: if the main #[cfg] syntax decided to support non-string literals,
78                     // this should be changed as well.
79                     msg: "value of cfg option should be a string literal",
80                     span: lit.span,
81                 }),
82             },
83             MetaItemKind::List(ref items) => {
84                 let mut sub_cfgs = items.iter().map(Cfg::parse_nested);
85                 match name {
86                     sym::all => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)),
87                     sym::any => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)),
88                     sym::not => {
89                         if sub_cfgs.len() == 1 {
90                             Ok(!sub_cfgs.next().unwrap()?)
91                         } else {
92                             Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span })
93                         }
94                     }
95                     _ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }),
96                 }
97             }
98         }
99     }
100
101     /// Checks whether the given configuration can be matched in the current session.
102     ///
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 {
106         match *self {
107             Cfg::False => false,
108             Cfg::True => true,
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))
112             }
113             Cfg::Any(ref sub_cfgs) => {
114                 sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(parse_sess, features))
115             }
116             Cfg::Cfg(name, value) => parse_sess.config.contains(&(name, value)),
117         }
118     }
119
120     /// Whether the configuration consists of just `Cfg` or `Not`.
121     fn is_simple(&self) -> bool {
122         match *self {
123             Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
124             Cfg::All(..) | Cfg::Any(..) => false,
125         }
126     }
127
128     /// Whether the configuration consists of just `Cfg`, `Not` or `All`.
129     fn is_all(&self) -> bool {
130         match *self {
131             Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
132             Cfg::Any(..) => false,
133         }
134     }
135
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 = Display(self, Format::ShortHtml).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();
142             }
143         }
144         msg
145     }
146
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() { "with" } else { "on" };
150
151         let mut msg = format!(
152             "This is supported {} <strong>{}</strong>",
153             on,
154             Display(self, Format::LongHtml)
155         );
156         if self.should_append_only_to_description() {
157             msg.push_str(" only");
158         }
159         msg.push('.');
160         msg
161     }
162
163     /// Renders the configuration for long display, as a long plain text description.
164     pub(crate) fn render_long_plain(&self) -> String {
165         let on = if self.should_use_with_in_description() { "with" } else { "on" };
166
167         let mut msg = format!("This is supported {} {}", on, Display(self, Format::LongPlain));
168         if self.should_append_only_to_description() {
169             msg.push_str(" only");
170         }
171         msg
172     }
173
174     fn should_capitalize_first_letter(&self) -> bool {
175         match *self {
176             Cfg::False | Cfg::True | Cfg::Not(..) => true,
177             Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
178                 sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
179             }
180             Cfg::Cfg(name, _) => match name {
181                 sym::debug_assertions | sym::target_endian => true,
182                 _ => false,
183             },
184         }
185     }
186
187     fn should_append_only_to_description(&self) -> bool {
188         match *self {
189             Cfg::False | Cfg::True => false,
190             Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
191             Cfg::Not(ref child) => match **child {
192                 Cfg::Cfg(..) => true,
193                 _ => false,
194             },
195         }
196     }
197
198     fn should_use_with_in_description(&self) -> bool {
199         match *self {
200             Cfg::Cfg(name, _) if name == sym::target_feature => true,
201             _ => false,
202         }
203     }
204
205     /// Attempt to simplify this cfg by assuming that `assume` is already known to be true, will
206     /// return `None` if simplification managed to completely eliminate any requirements from this
207     /// `Cfg`.
208     ///
209     /// See `tests::test_simplify_with` for examples.
210     pub(crate) fn simplify_with(&self, assume: &Cfg) -> Option<Cfg> {
211         if self == assume {
212             return None;
213         }
214
215         if let Cfg::All(a) = self {
216             let mut sub_cfgs: Vec<Cfg> = if let Cfg::All(b) = assume {
217                 a.iter().filter(|a| !b.contains(a)).cloned().collect()
218             } else {
219                 a.iter().filter(|&a| a != assume).cloned().collect()
220             };
221             let len = sub_cfgs.len();
222             return match len {
223                 0 => None,
224                 1 => sub_cfgs.pop(),
225                 _ => Some(Cfg::All(sub_cfgs)),
226             };
227         } else if let Cfg::All(b) = assume {
228             if b.contains(self) {
229                 return None;
230             }
231         }
232
233         Some(self.clone())
234     }
235 }
236
237 impl ops::Not for Cfg {
238     type Output = Cfg;
239     fn not(self) -> Cfg {
240         match self {
241             Cfg::False => Cfg::True,
242             Cfg::True => Cfg::False,
243             Cfg::Not(cfg) => *cfg,
244             s => Cfg::Not(Box::new(s)),
245         }
246     }
247 }
248
249 impl ops::BitAndAssign for Cfg {
250     fn bitand_assign(&mut self, other: Cfg) {
251         match (self, other) {
252             (&mut Cfg::False, _) | (_, Cfg::True) => {}
253             (s, Cfg::False) => *s = Cfg::False,
254             (s @ &mut Cfg::True, b) => *s = b,
255             (&mut Cfg::All(ref mut a), Cfg::All(ref mut b)) => {
256                 for c in b.drain(..) {
257                     if !a.contains(&c) {
258                         a.push(c);
259                     }
260                 }
261             }
262             (&mut Cfg::All(ref mut a), ref mut b) => {
263                 if !a.contains(b) {
264                     a.push(mem::replace(b, Cfg::True));
265                 }
266             }
267             (s, Cfg::All(mut a)) => {
268                 let b = mem::replace(s, Cfg::True);
269                 if !a.contains(&b) {
270                     a.push(b);
271                 }
272                 *s = Cfg::All(a);
273             }
274             (s, b) => {
275                 if *s != b {
276                     let a = mem::replace(s, Cfg::True);
277                     *s = Cfg::All(vec![a, b]);
278                 }
279             }
280         }
281     }
282 }
283
284 impl ops::BitAnd for Cfg {
285     type Output = Cfg;
286     fn bitand(mut self, other: Cfg) -> Cfg {
287         self &= other;
288         self
289     }
290 }
291
292 impl ops::BitOrAssign for Cfg {
293     fn bitor_assign(&mut self, other: Cfg) {
294         match (self, other) {
295             (&mut Cfg::True, _) | (_, Cfg::False) => {}
296             (s, Cfg::True) => *s = Cfg::True,
297             (s @ &mut Cfg::False, b) => *s = b,
298             (&mut Cfg::Any(ref mut a), Cfg::Any(ref mut b)) => {
299                 for c in b.drain(..) {
300                     if !a.contains(&c) {
301                         a.push(c);
302                     }
303                 }
304             }
305             (&mut Cfg::Any(ref mut a), ref mut b) => {
306                 if !a.contains(b) {
307                     a.push(mem::replace(b, Cfg::True));
308                 }
309             }
310             (s, Cfg::Any(mut a)) => {
311                 let b = mem::replace(s, Cfg::True);
312                 if !a.contains(&b) {
313                     a.push(b);
314                 }
315                 *s = Cfg::Any(a);
316             }
317             (s, b) => {
318                 if *s != b {
319                     let a = mem::replace(s, Cfg::True);
320                     *s = Cfg::Any(vec![a, b]);
321                 }
322             }
323         }
324     }
325 }
326
327 impl ops::BitOr for Cfg {
328     type Output = Cfg;
329     fn bitor(mut self, other: Cfg) -> Cfg {
330         self |= other;
331         self
332     }
333 }
334
335 #[derive(Clone, Copy)]
336 enum Format {
337     LongHtml,
338     LongPlain,
339     ShortHtml,
340 }
341
342 impl Format {
343     fn is_long(self) -> bool {
344         match self {
345             Format::LongHtml | Format::LongPlain => true,
346             Format::ShortHtml => false,
347         }
348     }
349
350     fn is_html(self) -> bool {
351         match self {
352             Format::LongHtml | Format::ShortHtml => true,
353             Format::LongPlain => false,
354         }
355     }
356 }
357
358 /// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
359 struct Display<'a>(&'a Cfg, Format);
360
361 fn write_with_opt_paren<T: fmt::Display>(
362     fmt: &mut fmt::Formatter<'_>,
363     has_paren: bool,
364     obj: T,
365 ) -> fmt::Result {
366     if has_paren {
367         fmt.write_char('(')?;
368     }
369     obj.fmt(fmt)?;
370     if has_paren {
371         fmt.write_char(')')?;
372     }
373     Ok(())
374 }
375
376 impl<'a> fmt::Display for Display<'a> {
377     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
378         match *self.0 {
379             Cfg::Not(ref child) => match **child {
380                 Cfg::Any(ref sub_cfgs) => {
381                     let separator =
382                         if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
383                     for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
384                         fmt.write_str(if i == 0 { "neither " } else { separator })?;
385                         write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
386                     }
387                     Ok(())
388                 }
389                 ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Display(simple, self.1)),
390                 ref c => write!(fmt, "not ({})", Display(c, self.1)),
391             },
392
393             Cfg::Any(ref sub_cfgs) => {
394                 let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };
395
396                 let short_longhand = self.1.is_long() && {
397                     let all_crate_features = sub_cfgs
398                         .iter()
399                         .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
400                     let all_target_features = sub_cfgs
401                         .iter()
402                         .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
403
404                     if all_crate_features {
405                         fmt.write_str("crate features ")?;
406                         true
407                     } else if all_target_features {
408                         fmt.write_str("target features ")?;
409                         true
410                     } else {
411                         false
412                     }
413                 };
414
415                 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
416                     if i != 0 {
417                         fmt.write_str(separator)?;
418                     }
419                     if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
420                         if self.1.is_html() {
421                             write!(fmt, "<code>{}</code>", feat)?;
422                         } else {
423                             write!(fmt, "`{}`", feat)?;
424                         }
425                     } else {
426                         write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
427                     }
428                 }
429                 Ok(())
430             }
431
432             Cfg::All(ref sub_cfgs) => {
433                 let short_longhand = self.1.is_long() && {
434                     let all_crate_features = sub_cfgs
435                         .iter()
436                         .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
437                     let all_target_features = sub_cfgs
438                         .iter()
439                         .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
440
441                     if all_crate_features {
442                         fmt.write_str("crate features ")?;
443                         true
444                     } else if all_target_features {
445                         fmt.write_str("target features ")?;
446                         true
447                     } else {
448                         false
449                     }
450                 };
451
452                 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
453                     if i != 0 {
454                         fmt.write_str(" and ")?;
455                     }
456                     if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
457                         if self.1.is_html() {
458                             write!(fmt, "<code>{}</code>", feat)?;
459                         } else {
460                             write!(fmt, "`{}`", feat)?;
461                         }
462                     } else {
463                         write_with_opt_paren(fmt, !sub_cfg.is_simple(), Display(sub_cfg, self.1))?;
464                     }
465                 }
466                 Ok(())
467             }
468
469             Cfg::True => fmt.write_str("everywhere"),
470             Cfg::False => fmt.write_str("nowhere"),
471
472             Cfg::Cfg(name, value) => {
473                 let human_readable = match (name, value) {
474                     (sym::unix, None) => "Unix",
475                     (sym::windows, None) => "Windows",
476                     (sym::debug_assertions, None) => "debug-assertions enabled",
477                     (sym::target_os, Some(os)) => match &*os.as_str() {
478                         "android" => "Android",
479                         "dragonfly" => "DragonFly BSD",
480                         "emscripten" => "Emscripten",
481                         "freebsd" => "FreeBSD",
482                         "fuchsia" => "Fuchsia",
483                         "haiku" => "Haiku",
484                         "hermit" => "HermitCore",
485                         "illumos" => "illumos",
486                         "ios" => "iOS",
487                         "l4re" => "L4Re",
488                         "linux" => "Linux",
489                         "macos" => "macOS",
490                         "netbsd" => "NetBSD",
491                         "openbsd" => "OpenBSD",
492                         "redox" => "Redox",
493                         "solaris" => "Solaris",
494                         "windows" => "Windows",
495                         _ => "",
496                     },
497                     (sym::target_arch, Some(arch)) => match &*arch.as_str() {
498                         "aarch64" => "AArch64",
499                         "arm" => "ARM",
500                         "asmjs" => "JavaScript",
501                         "mips" => "MIPS",
502                         "mips64" => "MIPS-64",
503                         "msp430" => "MSP430",
504                         "powerpc" => "PowerPC",
505                         "powerpc64" => "PowerPC-64",
506                         "s390x" => "s390x",
507                         "sparc64" => "SPARC64",
508                         "wasm32" => "WebAssembly",
509                         "x86" => "x86",
510                         "x86_64" => "x86-64",
511                         _ => "",
512                     },
513                     (sym::target_vendor, Some(vendor)) => match &*vendor.as_str() {
514                         "apple" => "Apple",
515                         "pc" => "PC",
516                         "rumprun" => "Rumprun",
517                         "sun" => "Sun",
518                         "fortanix" => "Fortanix",
519                         _ => "",
520                     },
521                     (sym::target_env, Some(env)) => match &*env.as_str() {
522                         "gnu" => "GNU",
523                         "msvc" => "MSVC",
524                         "musl" => "musl",
525                         "newlib" => "Newlib",
526                         "uclibc" => "uClibc",
527                         "sgx" => "SGX",
528                         _ => "",
529                     },
530                     (sym::target_endian, Some(endian)) => return write!(fmt, "{}-endian", endian),
531                     (sym::target_pointer_width, Some(bits)) => return write!(fmt, "{}-bit", bits),
532                     (sym::target_feature, Some(feat)) => match self.1 {
533                         Format::LongHtml => {
534                             return write!(fmt, "target feature <code>{}</code>", feat);
535                         }
536                         Format::LongPlain => return write!(fmt, "target feature `{}`", feat),
537                         Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
538                     },
539                     (sym::feature, Some(feat)) => match self.1 {
540                         Format::LongHtml => {
541                             return write!(fmt, "crate feature <code>{}</code>", feat);
542                         }
543                         Format::LongPlain => return write!(fmt, "crate feature `{}`", feat),
544                         Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
545                     },
546                     _ => "",
547                 };
548                 if !human_readable.is_empty() {
549                     fmt.write_str(human_readable)
550                 } else if let Some(v) = value {
551                     if self.1.is_html() {
552                         write!(
553                             fmt,
554                             r#"<code>{}="{}"</code>"#,
555                             Escape(&name.as_str()),
556                             Escape(&v.as_str())
557                         )
558                     } else {
559                         write!(fmt, r#"`{}="{}"`"#, name, v)
560                     }
561                 } else if self.1.is_html() {
562                     write!(fmt, "<code>{}</code>", Escape(&name.as_str()))
563                 } else {
564                     write!(fmt, "`{}`", name)
565                 }
566             }
567         }
568     }
569 }