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