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