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 Ps3 => "Play Station 3",
689 Psp => "Play Station Portable",
690 Psx => "Play Station 1",
691 Qnx => "QNX SDP 8.0+",
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<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>(
839 attrs: I,
840 tcx: TyCtxt<'_>,
841 cfg_info: &mut CfgInfo,
842) -> Option<Arc<Cfg>> {
843 fn check_changed_auto_active_status(
844 changed_auto_active_status: &mut Option<rustc_span::Span>,
845 attr_span: Span,
846 cfg_info: &mut CfgInfo,
847 tcx: TyCtxt<'_>,
848 new_value: bool,
849 ) -> bool {
850 if let Some(first_change) = changed_auto_active_status {
851 if cfg_info.auto_cfg_active != new_value {
852 tcx.sess
853 .dcx()
854 .struct_span_err(
855 vec![*first_change, attr_span],
856 "`auto_cfg` was disabled and enabled more than once on the same item",
857 )
858 .emit();
859 return true;
860 }
861 } else {
862 *changed_auto_active_status = Some(attr_span);
863 }
864 cfg_info.auto_cfg_active = new_value;
865 false
866 }
867
868 let mut doc_cfg = attrs
869 .clone()
870 .filter_map(|attr| match attr {
871 Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => Some(d),
872 _ => None,
873 })
874 .peekable();
875 if doc_cfg.peek().is_some() {
877 if !cfg_info.parent_is_doc_cfg {
879 cfg_info.current_cfg = Cfg(CfgEntry::Bool(true, DUMMY_SP));
880 cfg_info.parent_is_doc_cfg = true;
881 }
882 for attr in doc_cfg {
883 for new_cfg in attr.cfg.clone() {
884 cfg_info.current_cfg &= Cfg(new_cfg);
885 }
886 }
887 } else {
888 cfg_info.parent_is_doc_cfg = false;
889 }
890
891 let mut changed_auto_active_status = None;
892
893 for attr in attrs {
895 if let Attribute::Parsed(AttributeKind::Doc(d)) = attr {
896 for (new_value, span) in &d.auto_cfg_change {
897 if check_changed_auto_active_status(
898 &mut changed_auto_active_status,
899 *span,
900 cfg_info,
901 tcx,
902 *new_value,
903 ) {
904 return None;
905 }
906 }
907 if let Some((_, span)) = d.auto_cfg.first() {
908 if check_changed_auto_active_status(
909 &mut changed_auto_active_status,
910 *span,
911 cfg_info,
912 tcx,
913 true,
914 ) {
915 return None;
916 }
917 for (value, _) in &d.auto_cfg {
918 handle_auto_cfg_hide_show(cfg_info, value);
919 }
920 }
921 } else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr {
922 for (feature, _) in features {
925 cfg_info.current_cfg &= Cfg(CfgEntry::NameValue {
926 name: sym::target_feature,
927 value: Some(*feature),
928 span: DUMMY_SP,
929 });
930 }
931 continue;
932 } else if !cfg_info.parent_is_doc_cfg
933 && let hir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr
934 {
935 for (new_cfg, _) in cfgs {
936 cfg_info.current_cfg &= Cfg(new_cfg.clone());
937 }
938 }
939 }
940
941 if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg {
944 None
945 } else if cfg_info.parent_is_doc_cfg {
946 if matches!(cfg_info.current_cfg.0, CfgEntry::Bool(true, _)) {
947 None
948 } else {
949 let mut cfg = cfg_info.current_cfg.clone();
950 cfg.sort_for_rendering();
951 Some(Arc::new(cfg))
952 }
953 } else {
954 match strip_hidden(&cfg_info.current_cfg.0, &cfg_info.hidden_cfg) {
957 None | Some(CfgEntry::Bool(true, _)) => None,
958 Some(cfg_entry) => {
959 let mut cfg = Cfg(cfg_entry);
960 cfg.sort_for_rendering();
961 Some(Arc::new(cfg))
962 }
963 }
964 }
965}