1use 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#[cfg_attr(test, derive(PartialEq))]
33pub(crate) struct Cfg(pub(crate) CfgEntry);
34
35#[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 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 *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
116fn 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
127fn 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 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 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 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 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 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 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
465struct 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 [] => 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 [] => 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 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 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 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 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 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 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 Unspecified | Other(_) => return None,
780 })
781}
782
783#[derive(Clone, Debug)]
785pub(crate) struct CfgInfo {
786 hidden_cfg: FxHashMap<Symbol, DocCfgHide>,
789 pub(crate) current_cfg: Cfg,
792 auto_cfg_active: bool,
794 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
815fn 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 doc_cfg.peek().is_some() {
876 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 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 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 !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 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}