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