1use 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#[cfg_attr(test, derive(PartialEq))]
31pub(crate) struct Cfg(pub(crate) CfgEntry);
32
33#[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 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 *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
114fn 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
125fn 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 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 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 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 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 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 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
463struct 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 [] => 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 [] => 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 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 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 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 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 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 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 Unspecified | Other(_) => return None,
779 })
780}
781
782#[derive(Clone, Debug)]
784pub(crate) struct CfgInfo {
785 hidden_cfg: FxHashMap<Symbol, DocCfgHide>,
788 pub(crate) current_cfg: Cfg,
791 auto_cfg_active: bool,
793 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
814fn 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 doc_cfg.peek().is_some() {
878 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 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 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 !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 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}