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::sync::Arc;
7use std::{fmt, mem, ops};
8
9use itertools::Either;
10use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
11use rustc_data_structures::fx::{FxHashMap, FxHashSet};
12use rustc_hir::attrs::AttributeKind;
13use rustc_middle::ty::TyCtxt;
14use rustc_session::parse::ParseSess;
15use rustc_span::Span;
16use rustc_span::symbol::{Symbol, sym};
17use {rustc_ast as ast, rustc_hir as hir};
18
19use crate::display::{Joined as _, MaybeDisplay, Wrapped};
20use crate::html::escape::Escape;
21
22#[cfg(test)]
23mod tests;
24
25#[derive(Clone, Debug, PartialEq, Eq, Hash)]
26pub(crate) enum Cfg {
27    /// Accepts all configurations.
28    True,
29    /// Denies all configurations.
30    False,
31    /// A generic configuration option, e.g., `test` or `target_os = "linux"`.
32    Cfg(Symbol, Option<Symbol>),
33    /// Negates a configuration requirement, i.e., `not(x)`.
34    Not(Box<Cfg>),
35    /// Union of a list of configuration requirements, i.e., `any(...)`.
36    Any(Vec<Cfg>),
37    /// Intersection of a list of configuration requirements, i.e., `all(...)`.
38    All(Vec<Cfg>),
39}
40
41#[derive(PartialEq, Debug)]
42pub(crate) struct InvalidCfgError {
43    pub(crate) msg: &'static str,
44    pub(crate) span: Span,
45}
46
47impl Cfg {
48    /// Parses a `MetaItemInner` into a `Cfg`.
49    fn parse_nested(
50        nested_cfg: &MetaItemInner,
51        exclude: &FxHashSet<Cfg>,
52    ) -> Result<Option<Cfg>, InvalidCfgError> {
53        match nested_cfg {
54            MetaItemInner::MetaItem(cfg) => Cfg::parse_without(cfg, exclude),
55            MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => match *b {
56                true => Ok(Some(Cfg::True)),
57                false => Ok(Some(Cfg::False)),
58            },
59            MetaItemInner::Lit(lit) => {
60                Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
61            }
62        }
63    }
64
65    pub(crate) fn parse_without(
66        cfg: &MetaItem,
67        exclude: &FxHashSet<Cfg>,
68    ) -> Result<Option<Cfg>, InvalidCfgError> {
69        let name = match cfg.ident() {
70            Some(ident) => ident.name,
71            None => {
72                return Err(InvalidCfgError {
73                    msg: "expected a single identifier",
74                    span: cfg.span,
75                });
76            }
77        };
78        match cfg.kind {
79            MetaItemKind::Word => {
80                let cfg = Cfg::Cfg(name, None);
81                if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
82            }
83            MetaItemKind::NameValue(ref lit) => match lit.kind {
84                LitKind::Str(value, _) => {
85                    let cfg = Cfg::Cfg(name, Some(value));
86                    if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
87                }
88                _ => Err(InvalidCfgError {
89                    // FIXME: if the main #[cfg] syntax decided to support non-string literals,
90                    // this should be changed as well.
91                    msg: "value of cfg option should be a string literal",
92                    span: lit.span,
93                }),
94            },
95            MetaItemKind::List(ref items) => {
96                let orig_len = items.len();
97                let mut sub_cfgs =
98                    items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose());
99                let ret = match name {
100                    sym::all => sub_cfgs.try_fold(Cfg::True, |x, y| Ok(x & y?)),
101                    sym::any => sub_cfgs.try_fold(Cfg::False, |x, y| Ok(x | y?)),
102                    sym::not => {
103                        if orig_len == 1 {
104                            let mut sub_cfgs = sub_cfgs.collect::<Vec<_>>();
105                            if sub_cfgs.len() == 1 {
106                                Ok(!sub_cfgs.pop().unwrap()?)
107                            } else {
108                                return Ok(None);
109                            }
110                        } else {
111                            Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span })
112                        }
113                    }
114                    _ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }),
115                };
116                match ret {
117                    Ok(c) => Ok(Some(c)),
118                    Err(e) => Err(e),
119                }
120            }
121        }
122    }
123
124    /// Parses a `MetaItem` into a `Cfg`.
125    ///
126    /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or
127    /// `target_os = "redox"`.
128    ///
129    /// If the content is not properly formatted, it will return an error indicating what and where
130    /// the error is.
131    pub(crate) fn parse(cfg: &MetaItemInner) -> Result<Cfg, InvalidCfgError> {
132        Self::parse_nested(cfg, &FxHashSet::default()).map(|ret| ret.unwrap())
133    }
134
135    /// Checks whether the given configuration can be matched in the current session.
136    ///
137    /// Equivalent to `attr::cfg_matches`.
138    pub(crate) fn matches(&self, psess: &ParseSess) -> bool {
139        match *self {
140            Cfg::False => false,
141            Cfg::True => true,
142            Cfg::Not(ref child) => !child.matches(psess),
143            Cfg::All(ref sub_cfgs) => sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(psess)),
144            Cfg::Any(ref sub_cfgs) => sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(psess)),
145            Cfg::Cfg(name, value) => psess.config.contains(&(name, value)),
146        }
147    }
148
149    /// Whether the configuration consists of just `Cfg` or `Not`.
150    fn is_simple(&self) -> bool {
151        match self {
152            Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
153            Cfg::All(..) | Cfg::Any(..) => false,
154        }
155    }
156
157    /// Whether the configuration consists of just `Cfg`, `Not` or `All`.
158    fn is_all(&self) -> bool {
159        match self {
160            Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
161            Cfg::Any(..) => false,
162        }
163    }
164
165    /// Renders the configuration for human display, as a short HTML description.
166    pub(crate) fn render_short_html(&self) -> String {
167        let mut msg = Display(self, Format::ShortHtml).to_string();
168        if self.should_capitalize_first_letter()
169            && let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric())
170        {
171            msg[i..i + 1].make_ascii_uppercase();
172        }
173        msg
174    }
175
176    fn render_long_inner(&self, format: Format) -> String {
177        let on = if self.omit_preposition() {
178            " "
179        } else if self.should_use_with_in_description() {
180            " with "
181        } else {
182            " on "
183        };
184
185        let mut msg = if matches!(format, Format::LongHtml) {
186            format!("Available{on}<strong>{}</strong>", Display(self, format))
187        } else {
188            format!("Available{on}{}", Display(self, format))
189        };
190        if self.should_append_only_to_description() {
191            msg.push_str(" only");
192        }
193        msg
194    }
195
196    /// Renders the configuration for long display, as a long HTML description.
197    pub(crate) fn render_long_html(&self) -> String {
198        let mut msg = self.render_long_inner(Format::LongHtml);
199        msg.push('.');
200        msg
201    }
202
203    /// Renders the configuration for long display, as a long plain text description.
204    pub(crate) fn render_long_plain(&self) -> String {
205        self.render_long_inner(Format::LongPlain)
206    }
207
208    fn should_capitalize_first_letter(&self) -> bool {
209        match *self {
210            Cfg::False | Cfg::True | Cfg::Not(..) => true,
211            Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
212                sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
213            }
214            Cfg::Cfg(name, _) => name == sym::debug_assertions || name == sym::target_endian,
215        }
216    }
217
218    fn should_append_only_to_description(&self) -> bool {
219        match self {
220            Cfg::False | Cfg::True => false,
221            Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
222            Cfg::Not(box Cfg::Cfg(..)) => true,
223            Cfg::Not(..) => false,
224        }
225    }
226
227    fn should_use_with_in_description(&self) -> bool {
228        matches!(self, Cfg::Cfg(sym::target_feature, _))
229    }
230
231    /// Attempt to simplify this cfg by assuming that `assume` is already known to be true, will
232    /// return `None` if simplification managed to completely eliminate any requirements from this
233    /// `Cfg`.
234    ///
235    /// See `tests::test_simplify_with` for examples.
236    pub(crate) fn simplify_with(&self, assume: &Self) -> Option<Self> {
237        if self == assume {
238            None
239        } else if let Cfg::All(a) = self {
240            let mut sub_cfgs: Vec<Cfg> = if let Cfg::All(b) = assume {
241                a.iter().filter(|a| !b.contains(a)).cloned().collect()
242            } else {
243                a.iter().filter(|&a| a != assume).cloned().collect()
244            };
245            let len = sub_cfgs.len();
246            match len {
247                0 => None,
248                1 => sub_cfgs.pop(),
249                _ => Some(Cfg::All(sub_cfgs)),
250            }
251        } else if let Cfg::All(b) = assume
252            && b.contains(self)
253        {
254            None
255        } else {
256            Some(self.clone())
257        }
258    }
259
260    fn omit_preposition(&self) -> bool {
261        matches!(self, Cfg::True | Cfg::False)
262    }
263
264    pub(crate) fn strip_hidden(&self, hidden: &FxHashSet<Cfg>) -> Option<Self> {
265        match self {
266            Self::True | Self::False => Some(self.clone()),
267            Self::Cfg(..) => {
268                if !hidden.contains(self) {
269                    Some(self.clone())
270                } else {
271                    None
272                }
273            }
274            Self::Not(cfg) => {
275                if let Some(cfg) = cfg.strip_hidden(hidden) {
276                    Some(Self::Not(Box::new(cfg)))
277                } else {
278                    None
279                }
280            }
281            Self::Any(cfgs) => {
282                let cfgs =
283                    cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>();
284                if cfgs.is_empty() { None } else { Some(Self::Any(cfgs)) }
285            }
286            Self::All(cfgs) => {
287                let cfgs =
288                    cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>();
289                if cfgs.is_empty() { None } else { Some(Self::All(cfgs)) }
290            }
291        }
292    }
293}
294
295impl ops::Not for Cfg {
296    type Output = Cfg;
297    fn not(self) -> Cfg {
298        match self {
299            Cfg::False => Cfg::True,
300            Cfg::True => Cfg::False,
301            Cfg::Not(cfg) => *cfg,
302            s => Cfg::Not(Box::new(s)),
303        }
304    }
305}
306
307impl ops::BitAndAssign for Cfg {
308    fn bitand_assign(&mut self, other: Cfg) {
309        match (self, other) {
310            (Cfg::False, _) | (_, Cfg::True) => {}
311            (s, Cfg::False) => *s = Cfg::False,
312            (s @ Cfg::True, b) => *s = b,
313            (Cfg::All(a), Cfg::All(ref mut b)) => {
314                for c in b.drain(..) {
315                    if !a.contains(&c) {
316                        a.push(c);
317                    }
318                }
319            }
320            (Cfg::All(a), ref mut b) => {
321                if !a.contains(b) {
322                    a.push(mem::replace(b, Cfg::True));
323                }
324            }
325            (s, Cfg::All(mut a)) => {
326                let b = mem::replace(s, Cfg::True);
327                if !a.contains(&b) {
328                    a.push(b);
329                }
330                *s = Cfg::All(a);
331            }
332            (s, b) => {
333                if *s != b {
334                    let a = mem::replace(s, Cfg::True);
335                    *s = Cfg::All(vec![a, b]);
336                }
337            }
338        }
339    }
340}
341
342impl ops::BitAnd for Cfg {
343    type Output = Cfg;
344    fn bitand(mut self, other: Cfg) -> Cfg {
345        self &= other;
346        self
347    }
348}
349
350impl ops::BitOrAssign for Cfg {
351    fn bitor_assign(&mut self, other: Cfg) {
352        match (self, other) {
353            (Cfg::True, _) | (_, Cfg::False) | (_, Cfg::True) => {}
354            (s @ Cfg::False, b) => *s = b,
355            (Cfg::Any(a), Cfg::Any(ref mut b)) => {
356                for c in b.drain(..) {
357                    if !a.contains(&c) {
358                        a.push(c);
359                    }
360                }
361            }
362            (Cfg::Any(a), ref mut b) => {
363                if !a.contains(b) {
364                    a.push(mem::replace(b, Cfg::True));
365                }
366            }
367            (s, Cfg::Any(mut a)) => {
368                let b = mem::replace(s, Cfg::True);
369                if !a.contains(&b) {
370                    a.push(b);
371                }
372                *s = Cfg::Any(a);
373            }
374            (s, b) => {
375                if *s != b {
376                    let a = mem::replace(s, Cfg::True);
377                    *s = Cfg::Any(vec![a, b]);
378                }
379            }
380        }
381    }
382}
383
384impl ops::BitOr for Cfg {
385    type Output = Cfg;
386    fn bitor(mut self, other: Cfg) -> Cfg {
387        self |= other;
388        self
389    }
390}
391
392#[derive(Clone, Copy)]
393enum Format {
394    LongHtml,
395    LongPlain,
396    ShortHtml,
397}
398
399impl Format {
400    fn is_long(self) -> bool {
401        match self {
402            Format::LongHtml | Format::LongPlain => true,
403            Format::ShortHtml => false,
404        }
405    }
406
407    fn is_html(self) -> bool {
408        match self {
409            Format::LongHtml | Format::ShortHtml => true,
410            Format::LongPlain => false,
411        }
412    }
413
414    fn escape(self, s: &str) -> impl fmt::Display {
415        if self.is_html() { Either::Left(Escape(s)) } else { Either::Right(s) }
416    }
417}
418
419/// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
420struct Display<'a>(&'a Cfg, Format);
421
422impl Display<'_> {
423    fn code_wrappers(&self) -> Wrapped<&'static str> {
424        if self.1.is_html() { Wrapped::with("<code>", "</code>") } else { Wrapped::with("`", "`") }
425    }
426
427    fn display_sub_cfgs(
428        &self,
429        fmt: &mut fmt::Formatter<'_>,
430        sub_cfgs: &[Cfg],
431        separator: &str,
432    ) -> fmt::Result {
433        use fmt::Display as _;
434
435        let short_longhand = self.1.is_long() && {
436            let all_crate_features =
437                sub_cfgs.iter().all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
438            let all_target_features = sub_cfgs
439                .iter()
440                .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
441
442            if all_crate_features {
443                fmt.write_str("crate features ")?;
444                true
445            } else if all_target_features {
446                fmt.write_str("target features ")?;
447                true
448            } else {
449                false
450            }
451        };
452
453        fmt::from_fn(|f| {
454            sub_cfgs
455                .iter()
456                .map(|sub_cfg| {
457                    if let Cfg::Cfg(_, Some(feat)) = sub_cfg
458                        && short_longhand
459                    {
460                        Either::Left(self.code_wrappers().wrap(feat))
461                    } else {
462                        Either::Right(
463                            Wrapped::with_parens()
464                                .when(!sub_cfg.is_all())
465                                .wrap(Display(sub_cfg, self.1)),
466                        )
467                    }
468                })
469                .joined(separator, f)
470        })
471        .fmt(fmt)?;
472
473        Ok(())
474    }
475}
476
477impl fmt::Display for Display<'_> {
478    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
479        match self.0 {
480            Cfg::Not(box Cfg::Any(sub_cfgs)) => {
481                let separator =
482                    if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
483                fmt.write_str("neither ")?;
484
485                sub_cfgs
486                    .iter()
487                    .map(|sub_cfg| {
488                        Wrapped::with_parens()
489                            .when(!sub_cfg.is_all())
490                            .wrap(Display(sub_cfg, self.1))
491                    })
492                    .joined(separator, fmt)
493            }
494            Cfg::Not(box simple @ Cfg::Cfg(..)) => write!(fmt, "non-{}", Display(simple, self.1)),
495            Cfg::Not(box c) => write!(fmt, "not ({})", Display(c, self.1)),
496
497            Cfg::Any(sub_cfgs) => {
498                let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };
499                self.display_sub_cfgs(fmt, sub_cfgs, separator)
500            }
501            Cfg::All(sub_cfgs) => self.display_sub_cfgs(fmt, sub_cfgs, " and "),
502
503            Cfg::True => fmt.write_str("everywhere"),
504            Cfg::False => fmt.write_str("nowhere"),
505
506            &Cfg::Cfg(name, value) => {
507                let human_readable = match (name, value) {
508                    (sym::unix, None) => "Unix",
509                    (sym::windows, None) => "Windows",
510                    (sym::debug_assertions, None) => "debug-assertions enabled",
511                    (sym::target_os, Some(os)) => match os.as_str() {
512                        "android" => "Android",
513                        "cygwin" => "Cygwin",
514                        "dragonfly" => "DragonFly BSD",
515                        "emscripten" => "Emscripten",
516                        "freebsd" => "FreeBSD",
517                        "fuchsia" => "Fuchsia",
518                        "haiku" => "Haiku",
519                        "hermit" => "HermitCore",
520                        "illumos" => "illumos",
521                        "ios" => "iOS",
522                        "l4re" => "L4Re",
523                        "linux" => "Linux",
524                        "macos" => "macOS",
525                        "netbsd" => "NetBSD",
526                        "openbsd" => "OpenBSD",
527                        "redox" => "Redox",
528                        "solaris" => "Solaris",
529                        "tvos" => "tvOS",
530                        "wasi" => "WASI",
531                        "watchos" => "watchOS",
532                        "windows" => "Windows",
533                        "visionos" => "visionOS",
534                        _ => "",
535                    },
536                    (sym::target_arch, Some(arch)) => match arch.as_str() {
537                        "aarch64" => "AArch64",
538                        "arm" => "ARM",
539                        "loongarch32" => "LoongArch LA32",
540                        "loongarch64" => "LoongArch LA64",
541                        "m68k" => "M68k",
542                        "csky" => "CSKY",
543                        "mips" => "MIPS",
544                        "mips32r6" => "MIPS Release 6",
545                        "mips64" => "MIPS-64",
546                        "mips64r6" => "MIPS-64 Release 6",
547                        "msp430" => "MSP430",
548                        "powerpc" => "PowerPC",
549                        "powerpc64" => "PowerPC-64",
550                        "riscv32" => "RISC-V RV32",
551                        "riscv64" => "RISC-V RV64",
552                        "s390x" => "s390x",
553                        "sparc64" => "SPARC64",
554                        "wasm32" | "wasm64" => "WebAssembly",
555                        "x86" => "x86",
556                        "x86_64" => "x86-64",
557                        _ => "",
558                    },
559                    (sym::target_vendor, Some(vendor)) => match vendor.as_str() {
560                        "apple" => "Apple",
561                        "pc" => "PC",
562                        "sun" => "Sun",
563                        "fortanix" => "Fortanix",
564                        _ => "",
565                    },
566                    (sym::target_env, Some(env)) => match env.as_str() {
567                        "gnu" => "GNU",
568                        "msvc" => "MSVC",
569                        "musl" => "musl",
570                        "newlib" => "Newlib",
571                        "uclibc" => "uClibc",
572                        "sgx" => "SGX",
573                        _ => "",
574                    },
575                    (sym::target_endian, Some(endian)) => return write!(fmt, "{endian}-endian"),
576                    (sym::target_pointer_width, Some(bits)) => return write!(fmt, "{bits}-bit"),
577                    (sym::target_feature, Some(feat)) => match self.1 {
578                        Format::LongHtml => {
579                            return write!(fmt, "target feature <code>{feat}</code>");
580                        }
581                        Format::LongPlain => return write!(fmt, "target feature `{feat}`"),
582                        Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
583                    },
584                    (sym::feature, Some(feat)) => match self.1 {
585                        Format::LongHtml => {
586                            return write!(fmt, "crate feature <code>{feat}</code>");
587                        }
588                        Format::LongPlain => return write!(fmt, "crate feature `{feat}`"),
589                        Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
590                    },
591                    _ => "",
592                };
593                if !human_readable.is_empty() {
594                    fmt.write_str(human_readable)
595                } else {
596                    let value = value
597                        .map(|v| fmt::from_fn(move |f| write!(f, "={}", self.1.escape(v.as_str()))))
598                        .maybe_display();
599                    self.code_wrappers()
600                        .wrap(format_args!("{}{value}", self.1.escape(name.as_str())))
601                        .fmt(fmt)
602                }
603            }
604        }
605    }
606}
607
608/// This type keeps track of (doc) cfg information as we go down the item tree.
609#[derive(Clone, Debug)]
610pub(crate) struct CfgInfo {
611    /// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active
612    /// `doc(auto_cfg(show(...)))` cfgs.
613    hidden_cfg: FxHashSet<Cfg>,
614    /// Current computed `cfg`. Each time we enter a new item, this field is updated as well while
615    /// taking into account the `hidden_cfg` information.
616    current_cfg: Cfg,
617    /// Whether the `doc(auto_cfg())` feature is enabled or not at this point.
618    auto_cfg_active: bool,
619    /// If the parent item used `doc(cfg(...))`, then we don't want to overwrite `current_cfg`,
620    /// instead we will concatenate with it. However, if it's not the case, we need to overwrite
621    /// `current_cfg`.
622    parent_is_doc_cfg: bool,
623}
624
625impl Default for CfgInfo {
626    fn default() -> Self {
627        Self {
628            hidden_cfg: FxHashSet::from_iter([
629                Cfg::Cfg(sym::test, None),
630                Cfg::Cfg(sym::doc, None),
631                Cfg::Cfg(sym::doctest, None),
632            ]),
633            current_cfg: Cfg::True,
634            auto_cfg_active: true,
635            parent_is_doc_cfg: false,
636        }
637    }
638}
639
640fn show_hide_show_conflict_error(
641    tcx: TyCtxt<'_>,
642    item_span: rustc_span::Span,
643    previous: rustc_span::Span,
644) {
645    let mut diag = tcx.sess.dcx().struct_span_err(
646        item_span,
647        format!(
648            "same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item"
649        ),
650    );
651    diag.span_note(previous, "first change was here");
652    diag.emit();
653}
654
655/// This functions updates the `hidden_cfg` field of the provided `cfg_info` argument.
656///
657/// It also checks if a same `cfg` is present in both `auto_cfg(hide(...))` and
658/// `auto_cfg(show(...))` on the same item and emits an error if it's the case.
659///
660/// Because we go through a list of `cfg`s, we keep track of the `cfg`s we saw in `new_show_attrs`
661/// and in `new_hide_attrs` arguments.
662fn handle_auto_cfg_hide_show(
663    tcx: TyCtxt<'_>,
664    cfg_info: &mut CfgInfo,
665    sub_attr: &MetaItemInner,
666    is_show: bool,
667    new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
668    new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
669) {
670    if let MetaItemInner::MetaItem(item) = sub_attr
671        && let MetaItemKind::List(items) = &item.kind
672    {
673        for item in items {
674            // FIXME: Report in case `Cfg::parse` reports an error?
675            if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) {
676                if is_show {
677                    if let Some(span) = new_hide_attrs.get(&(key, value)) {
678                        show_hide_show_conflict_error(tcx, item.span(), *span);
679                    } else {
680                        new_show_attrs.insert((key, value), item.span());
681                    }
682                    cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value));
683                } else {
684                    if let Some(span) = new_show_attrs.get(&(key, value)) {
685                        show_hide_show_conflict_error(tcx, item.span(), *span);
686                    } else {
687                        new_hide_attrs.insert((key, value), item.span());
688                    }
689                    cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value));
690                }
691            }
692        }
693    }
694}
695
696pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>(
697    attrs: I,
698    tcx: TyCtxt<'_>,
699    cfg_info: &mut CfgInfo,
700) -> Option<Arc<Cfg>> {
701    fn single<T: IntoIterator>(it: T) -> Option<T::Item> {
702        let mut iter = it.into_iter();
703        let item = iter.next()?;
704        if iter.next().is_some() {
705            return None;
706        }
707        Some(item)
708    }
709
710    fn check_changed_auto_active_status(
711        changed_auto_active_status: &mut Option<rustc_span::Span>,
712        attr: &ast::MetaItem,
713        cfg_info: &mut CfgInfo,
714        tcx: TyCtxt<'_>,
715        new_value: bool,
716    ) -> bool {
717        if let Some(first_change) = changed_auto_active_status {
718            if cfg_info.auto_cfg_active != new_value {
719                tcx.sess
720                    .dcx()
721                    .struct_span_err(
722                        vec![*first_change, attr.span],
723                        "`auto_cfg` was disabled and enabled more than once on the same item",
724                    )
725                    .emit();
726                return true;
727            }
728        } else {
729            *changed_auto_active_status = Some(attr.span);
730        }
731        cfg_info.auto_cfg_active = new_value;
732        false
733    }
734
735    let mut new_show_attrs = FxHashMap::default();
736    let mut new_hide_attrs = FxHashMap::default();
737
738    let mut doc_cfg = attrs
739        .clone()
740        .filter(|attr| attr.has_name(sym::doc))
741        .flat_map(|attr| attr.meta_item_list().unwrap_or_default())
742        .filter(|attr| attr.has_name(sym::cfg))
743        .peekable();
744    // If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes.
745    if doc_cfg.peek().is_some() {
746        let sess = tcx.sess;
747        // We overwrite existing `cfg`.
748        if !cfg_info.parent_is_doc_cfg {
749            cfg_info.current_cfg = Cfg::True;
750            cfg_info.parent_is_doc_cfg = true;
751        }
752        for attr in doc_cfg {
753            if let Some(cfg_mi) =
754                attr.meta_item().and_then(|attr| rustc_expand::config::parse_cfg(attr, sess))
755            {
756                match Cfg::parse(cfg_mi) {
757                    Ok(new_cfg) => cfg_info.current_cfg &= new_cfg,
758                    Err(e) => {
759                        sess.dcx().span_err(e.span, e.msg);
760                    }
761                }
762            }
763        }
764    } else {
765        cfg_info.parent_is_doc_cfg = false;
766    }
767
768    let mut changed_auto_active_status = None;
769
770    // We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes.
771    for attr in attrs {
772        if let Some(ident) = attr.ident()
773            && ident.name == sym::doc
774            && let Some(attrs) = attr.meta_item_list()
775        {
776            for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) {
777                let MetaItemInner::MetaItem(attr) = attr else {
778                    continue;
779                };
780                match &attr.kind {
781                    MetaItemKind::Word => {
782                        if check_changed_auto_active_status(
783                            &mut changed_auto_active_status,
784                            attr,
785                            cfg_info,
786                            tcx,
787                            true,
788                        ) {
789                            return None;
790                        }
791                    }
792                    MetaItemKind::NameValue(lit) => {
793                        if let LitKind::Bool(value) = lit.kind {
794                            if check_changed_auto_active_status(
795                                &mut changed_auto_active_status,
796                                attr,
797                                cfg_info,
798                                tcx,
799                                value,
800                            ) {
801                                return None;
802                            }
803                        }
804                    }
805                    MetaItemKind::List(sub_attrs) => {
806                        if check_changed_auto_active_status(
807                            &mut changed_auto_active_status,
808                            attr,
809                            cfg_info,
810                            tcx,
811                            true,
812                        ) {
813                            return None;
814                        }
815                        for sub_attr in sub_attrs.iter() {
816                            if let Some(ident) = sub_attr.ident()
817                                && (ident.name == sym::show || ident.name == sym::hide)
818                            {
819                                handle_auto_cfg_hide_show(
820                                    tcx,
821                                    cfg_info,
822                                    &sub_attr,
823                                    ident.name == sym::show,
824                                    &mut new_show_attrs,
825                                    &mut new_hide_attrs,
826                                );
827                            }
828                        }
829                    }
830                }
831            }
832        } else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr {
833            // Treat `#[target_feature(enable = "feat")]` attributes as if they were
834            // `#[doc(cfg(target_feature = "feat"))]` attributes as well.
835            for (feature, _) in features {
836                cfg_info.current_cfg &= Cfg::Cfg(sym::target_feature, Some(*feature));
837            }
838            continue;
839        } else if !cfg_info.parent_is_doc_cfg
840            && let Some(ident) = attr.ident()
841            && matches!(ident.name, sym::cfg | sym::cfg_trace)
842            && let Some(attr) = single(attr.meta_item_list()?)
843            && let Ok(new_cfg) = Cfg::parse(&attr)
844        {
845            cfg_info.current_cfg &= new_cfg;
846        }
847    }
848
849    // If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing
850    // to be done here.
851    if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg {
852        None
853    } else if cfg_info.parent_is_doc_cfg {
854        if cfg_info.current_cfg == Cfg::True {
855            None
856        } else {
857            Some(Arc::new(cfg_info.current_cfg.clone()))
858        }
859    } else {
860        // If `doc(auto_cfg)` feature is enabled, we want to collect all `cfg` items, we remove the
861        // hidden ones afterward.
862        match cfg_info.current_cfg.strip_hidden(&cfg_info.hidden_cfg) {
863            None | Some(Cfg::True) => None,
864            Some(cfg) => Some(Arc::new(cfg)),
865        }
866    }
867}