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