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