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