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 Hyperlight => "Hyperlight",
674 IOs => "iOS",
675 Illumos => "illumos",
676 L4Re => "L4Re",
677 Linux => "Linux",
678 LynxOs178 => "LynxOS-178",
679 MacOs => "macOS",
680 Managarm => "Managarm",
681 Motor => "Motor OS",
682 NetBsd => "NetBSD",
683 None => "bare-metal",
684 Nto => "QNX SDP 7.x",
685 NuttX => "NuttX",
686 OpenBsd => "OpenBSD",
687 Ps3 => "Play Station 3",
688 Psp => "Play Station Portable",
689 Psx => "Play Station 1",
690 Qnx => "QNX SDP 8.0+",
691 Qtee => "QTEE",
692 Qurt => "QuRT",
693 Redox => "Redox OS",
694 Rtems => "RTEMS OS",
695 Solaris => "Solaris",
696 SolidAsp3 => "SOLID ASP3",
697 TeeOs => "TEEOS",
698 Trusty => "Trusty",
699 TvOs => "tvOS",
700 Uefi => "UEFI",
701 VexOs => "VEXos",
702 VisionOs => "visionOS",
703 Vita => "Play Station Vita",
704 VxWorks => "VxWorks",
705 Wasi => "WASI",
706 WatchOs => "watchOS",
707 Windows => "Windows",
708 Xous => "Xous",
709 Zkvm => "zero knowledge Virtual Machine",
710 Unknown | Other(_) => return Option::None,
712 })
713}
714
715fn human_readable_target_arch(os: Symbol) -> Option<&'static str> {
716 let arch = spec::Arch::from_str(os.as_str()).ok()?;
717
718 use spec::Arch::*;
719 Some(match arch {
720 AArch64 => "AArch64",
722 AmdGpu => "AMD GPU",
723 Arm => "ARM",
724 Arm64EC => "ARM64EC",
725 Avr => "AVR",
726 Bpf => "BPF",
727 CSky => "C-SKY",
728 Hexagon => "Hexagon",
729 LoongArch32 => "LoongArch32",
730 LoongArch64 => "LoongArch64",
731 M68k => "Motorola 680x0",
732 Mips => "MIPS",
733 Mips32r6 => "MIPS release 6",
734 Mips64 => "MIPS-64",
735 Mips64r6 => "MIPS-64 release 6",
736 Msp430 => "MSP430",
737 Nvptx64 => "NVidia GPU",
738 PowerPC => "PowerPC",
739 PowerPC64 => "PowerPC64",
740 RiscV32 => "RISC-V RV32",
741 RiscV64 => "RISC-V RV64",
742 S390x => "s390x",
743 Sparc => "SPARC",
744 Sparc64 => "SPARC-64",
745 SpirV => "SPIR-V",
746 Wasm32 | Wasm64 => "WebAssembly",
747 X86 => "x86",
748 X86_64 => "x86-64",
749 Xtensa => "Xtensa",
750 Other(_) => return None,
752 })
753}
754
755fn human_readable_target_env(env: Symbol) -> Option<&'static str> {
756 let env = spec::Env::from_str(env.as_str()).ok()?;
757
758 use spec::Env::*;
759 Some(match env {
760 Gnu => "GNU",
762 MacAbi => "Catalyst",
763 Mlibc => "Managarm C Library",
764 Msvc => "MSVC",
765 Musl => "musl",
766 Newlib => "Newlib",
767 Nto70 => "QNX SDP 7.0",
768 Nto71 => "QNX SDP 7.1",
769 Nto71IoSock => "QNX SDP 7.1 with io-sock",
770 Ohos => "OpenHarmony",
771 P1 => "WASIp1",
772 P2 => "WASIp2",
773 P3 => "WASIp3",
774 Relibc => "relibc",
775 Sgx => "SGX",
776 Sim => "Simulator",
777 Uclibc => "uClibc",
778 V5 => "V5",
779 Unspecified | Other(_) => return None,
781 })
782}
783
784#[derive(Clone, Debug)]
786pub(crate) struct CfgInfo {
787 hidden_cfg: FxHashMap<Symbol, DocCfgHide>,
790 pub(crate) current_cfg: Cfg,
793 auto_cfg_active: bool,
795 parent_is_doc_cfg: bool,
799}
800
801impl Default for CfgInfo {
802 fn default() -> Self {
803 Self {
804 hidden_cfg: FxHashMap::from_iter([
805 (sym::test, DocCfgHide::new()),
806 (sym::doc, DocCfgHide::new()),
807 (sym::doctest, DocCfgHide::new()),
808 ]),
809 current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)),
810 auto_cfg_active: true,
811 parent_is_doc_cfg: false,
812 }
813 }
814}
815
816fn handle_auto_cfg_hide_show(cfg_info: &mut CfgInfo, attr: &CfgHideShow) {
821 for (cfg_name, value) in &attr.values {
822 if attr.kind == HideOrShow::Show {
823 cfg_info
824 .hidden_cfg
825 .entry(*cfg_name)
826 .and_modify(|entry| entry.remove(value))
827 .or_insert_with(|| value.into());
828 } else {
829 cfg_info
830 .hidden_cfg
831 .entry(*cfg_name)
832 .and_modify(|entry| entry.merge_with(value))
833 .or_insert_with(|| value.into());
834 }
835 }
836}
837
838pub(crate) fn extract_cfg_from_attrs<
839 'a,
840 I: Iterator<Item = &'a rustc_attr_ir::Attribute> + Clone,
841>(
842 attrs: I,
843 tcx: TyCtxt<'_>,
844 cfg_info: &mut CfgInfo,
845) -> Option<Arc<Cfg>> {
846 fn check_changed_auto_active_status(
847 changed_auto_active_status: &mut Option<rustc_span::Span>,
848 attr_span: Span,
849 cfg_info: &mut CfgInfo,
850 tcx: TyCtxt<'_>,
851 new_value: bool,
852 ) -> bool {
853 if let Some(first_change) = changed_auto_active_status {
854 if cfg_info.auto_cfg_active != new_value {
855 tcx.sess
856 .dcx()
857 .struct_span_err(
858 vec![*first_change, attr_span],
859 "`auto_cfg` was disabled and enabled more than once on the same item",
860 )
861 .emit();
862 return true;
863 }
864 } else {
865 *changed_auto_active_status = Some(attr_span);
866 }
867 cfg_info.auto_cfg_active = new_value;
868 false
869 }
870
871 let mut doc_cfg = attrs
872 .clone()
873 .filter_map(|attr| match attr {
874 rustc_attr_ir::Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => Some(d),
875 _ => None,
876 })
877 .peekable();
878 if doc_cfg.peek().is_some() {
880 if !cfg_info.parent_is_doc_cfg {
882 cfg_info.current_cfg = Cfg(CfgEntry::Bool(true, DUMMY_SP));
883 cfg_info.parent_is_doc_cfg = true;
884 }
885 for attr in doc_cfg {
886 for new_cfg in attr.cfg.clone() {
887 cfg_info.current_cfg &= Cfg(new_cfg);
888 }
889 }
890 } else {
891 cfg_info.parent_is_doc_cfg = false;
892 }
893
894 let mut changed_auto_active_status = None;
895
896 for attr in attrs {
898 if let rustc_attr_ir::Attribute::Parsed(AttributeKind::Doc(d)) = attr {
899 for (new_value, span) in &d.auto_cfg_change {
900 if check_changed_auto_active_status(
901 &mut changed_auto_active_status,
902 *span,
903 cfg_info,
904 tcx,
905 *new_value,
906 ) {
907 return None;
908 }
909 }
910 if let Some((_, span)) = d.auto_cfg.first() {
911 if check_changed_auto_active_status(
912 &mut changed_auto_active_status,
913 *span,
914 cfg_info,
915 tcx,
916 true,
917 ) {
918 return None;
919 }
920 for (value, _) in &d.auto_cfg {
921 handle_auto_cfg_hide_show(cfg_info, value);
922 }
923 }
924 } else if let rustc_attr_ir::Attribute::Parsed(AttributeKind::TargetFeature {
925 features,
926 ..
927 }) = attr
928 {
929 for (feature, _) in features {
932 cfg_info.current_cfg &= Cfg(CfgEntry::NameValue {
933 name: sym::target_feature,
934 value: Some(*feature),
935 span: DUMMY_SP,
936 });
937 }
938 continue;
939 } else if !cfg_info.parent_is_doc_cfg
940 && let rustc_attr_ir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr
941 {
942 for (new_cfg, _) in cfgs {
943 cfg_info.current_cfg &= Cfg(new_cfg.clone());
944 }
945 }
946 }
947
948 if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg {
951 None
952 } else if cfg_info.parent_is_doc_cfg {
953 if matches!(cfg_info.current_cfg.0, CfgEntry::Bool(true, _)) {
954 None
955 } else {
956 let mut cfg = cfg_info.current_cfg.clone();
957 cfg.sort_for_rendering();
958 Some(Arc::new(cfg))
959 }
960 } else {
961 match strip_hidden(&cfg_info.current_cfg.0, &cfg_info.hidden_cfg) {
964 None | Some(CfgEntry::Bool(true, _)) => None,
965 Some(cfg_entry) => {
966 let mut cfg = Cfg(cfg_entry);
967 cfg.sort_for_rendering();
968 Some(Arc::new(cfg))
969 }
970 }
971 }
972}