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