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