Skip to main content

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