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(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 match self.0 {
231 CfgEntry::Any(..)
232 | CfgEntry::All(..)
233 | CfgEntry::NameValue { .. }
234 | CfgEntry::Version(..)
235 | CfgEntry::Not(CfgEntry::NameValue { .. }, _) => true,
236 CfgEntry::Not(..) | CfgEntry::Bool(..) => false,
237 }
238 }
239
240 fn should_use_with_in_description(&self) -> bool {
241 matches!(self.0, CfgEntry::NameValue { name, .. } if name == sym::target_feature)
242 }
243
244 pub(crate) fn simplify_with(&self, assume: &Self) -> Option<Self> {
250 if self.0.is_equivalent_to(&assume.0) {
251 None
252 } else if let CfgEntry::All(a, _) = &self.0 {
253 let mut sub_cfgs: ThinVec<CfgEntry> = if let CfgEntry::All(b, _) = &assume.0 {
254 a.iter().filter(|a| !b.iter().any(|b| a.is_equivalent_to(b))).cloned().collect()
255 } else {
256 a.iter().filter(|&a| !a.is_equivalent_to(&assume.0)).cloned().collect()
257 };
258 let len = sub_cfgs.len();
259 match len {
260 0 => None,
261 1 => sub_cfgs.pop().map(Cfg),
262 _ => Some(Cfg(CfgEntry::All(sub_cfgs, DUMMY_SP))),
263 }
264 } else if let CfgEntry::All(b, _) = &assume.0
265 && b.iter().any(|b| b.is_equivalent_to(&self.0))
266 {
267 None
268 } else {
269 Some(self.clone())
270 }
271 }
272
273 pub(crate) fn sort_for_rendering(&mut self) {
279 fn sort_cfg_entry(cfg: &mut CfgEntry) {
280 match cfg {
281 CfgEntry::Any(sub_cfgs, _) | CfgEntry::All(sub_cfgs, _) => {
282 for sub_cfg in sub_cfgs.iter_mut() {
283 sort_cfg_entry(sub_cfg);
284 }
285
286 sub_cfgs.sort_by_cached_key(|a| {
287 (
288 cfg_category(a),
289 Display(a, Format::LongPlain).to_string().to_ascii_lowercase(),
290 )
291 });
292 }
293 CfgEntry::Not(box_cfg, _) => sort_cfg_entry(box_cfg),
294 _ => {}
295 }
296 }
297
298 fn cfg_category(cfg: &CfgEntry) -> u8 {
299 match cfg {
300 CfgEntry::NameValue { name, .. } if *name == sym::feature => 2,
301 CfgEntry::NameValue { name, .. } if *name == sym::target_feature => 1,
302 CfgEntry::NameValue { .. } | CfgEntry::Bool(..) => 0,
303 CfgEntry::Any(..) | CfgEntry::All(..) | CfgEntry::Not(..) => 3,
304 _ => 4,
305 }
306 }
307
308 sort_cfg_entry(&mut self.0);
309 }
310
311 fn omit_preposition(&self) -> bool {
312 matches!(self.0, CfgEntry::Bool(..))
313 }
314
315 pub(crate) fn inner(&self) -> &CfgEntry {
316 &self.0
317 }
318}
319
320impl ops::Not for Cfg {
321 type Output = Cfg;
322 fn not(self) -> Cfg {
323 Cfg(match self.0 {
324 CfgEntry::Bool(v, s) => CfgEntry::Bool(!v, s),
325 CfgEntry::Not(cfg, _) => *cfg,
326 s => CfgEntry::Not(Box::new(s), DUMMY_SP),
327 })
328 }
329}
330
331impl ops::BitAndAssign for Cfg {
332 fn bitand_assign(&mut self, other: Cfg) {
333 match (&mut self.0, other.0) {
334 (CfgEntry::Bool(false, _), _) | (_, CfgEntry::Bool(true, _)) => {}
335 (s, CfgEntry::Bool(false, _)) => *s = CfgEntry::Bool(false, DUMMY_SP),
336 (s @ CfgEntry::Bool(true, _), b) => *s = b,
337 (CfgEntry::All(a, _), CfgEntry::All(ref mut b, _)) => {
338 for c in b.drain(..) {
339 if !a.iter().any(|a| a.is_equivalent_to(&c)) {
340 a.push(c);
341 }
342 }
343 }
344 (CfgEntry::All(a, _), ref mut b) => {
345 if !a.iter().any(|a| a.is_equivalent_to(b)) {
346 a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP)));
347 }
348 }
349 (s, CfgEntry::All(mut a, _)) => {
350 let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
351 if !a.iter().any(|a| a.is_equivalent_to(&b)) {
352 a.push(b);
353 }
354 *s = CfgEntry::All(a, DUMMY_SP);
355 }
356 (s, b) => {
357 if !s.is_equivalent_to(&b) {
358 let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
359 *s = CfgEntry::All(thin_vec![a, b], DUMMY_SP);
360 }
361 }
362 }
363 }
364}
365
366impl ops::BitAnd for Cfg {
367 type Output = Cfg;
368 fn bitand(mut self, other: Cfg) -> Cfg {
369 self &= other;
370 self
371 }
372}
373
374impl ops::BitOrAssign for Cfg {
375 fn bitor_assign(&mut self, other: Cfg) {
376 match (&mut self.0, other.0) {
377 (CfgEntry::Bool(true, _), _)
378 | (_, CfgEntry::Bool(false, _))
379 | (_, CfgEntry::Bool(true, _)) => {}
380 (s @ CfgEntry::Bool(false, _), b) => *s = b,
381 (CfgEntry::Any(a, _), CfgEntry::Any(ref mut b, _)) => {
382 for c in b.drain(..) {
383 if !a.iter().any(|a| a.is_equivalent_to(&c)) {
384 a.push(c);
385 }
386 }
387 }
388 (CfgEntry::Any(a, _), ref mut b) => {
389 if !a.iter().any(|a| a.is_equivalent_to(b)) {
390 a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP)));
391 }
392 }
393 (s, CfgEntry::Any(mut a, _)) => {
394 let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
395 if !a.iter().any(|a| a.is_equivalent_to(&b)) {
396 a.push(b);
397 }
398 *s = CfgEntry::Any(a, DUMMY_SP);
399 }
400 (s, b) => {
401 if !s.is_equivalent_to(&b) {
402 let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
403 *s = CfgEntry::Any(thin_vec![a, b], DUMMY_SP);
404 }
405 }
406 }
407 }
408}
409
410impl ops::BitOr for Cfg {
411 type Output = Cfg;
412 fn bitor(mut self, other: Cfg) -> Cfg {
413 self |= other;
414 self
415 }
416}
417
418#[derive(Clone, Copy)]
419enum Format {
420 LongHtml,
421 LongPlain,
422 ShortHtml,
423}
424
425impl Format {
426 fn is_long(self) -> bool {
427 match self {
428 Format::LongHtml | Format::LongPlain => true,
429 Format::ShortHtml => false,
430 }
431 }
432
433 fn is_html(self) -> bool {
434 match self {
435 Format::LongHtml | Format::ShortHtml => true,
436 Format::LongPlain => false,
437 }
438 }
439
440 fn escape(self, s: &str) -> impl fmt::Display {
441 if self.is_html() { Either::Left(Escape(s)) } else { Either::Right(s) }
442 }
443}
444
445struct Display<'a>(&'a CfgEntry, Format);
447
448impl Display<'_> {
449 fn code_wrappers(&self) -> Wrapped<&'static str> {
450 if self.1.is_html() { Wrapped::with("<code>", "</code>") } else { Wrapped::with("`", "`") }
451 }
452
453 fn display_sub_cfgs(
454 &self,
455 fmt: &mut fmt::Formatter<'_>,
456 sub_cfgs: &[CfgEntry],
457 separator: &str,
458 ) -> fmt::Result {
459 use fmt::Display as _;
460
461 let short_longhand = self.1.is_long() && {
462 let all_crate_features = sub_cfgs.iter().all(|sub_cfg| {
463 matches!(sub_cfg, CfgEntry::NameValue { name: sym::feature, value: Some(_), .. })
464 });
465 let all_target_features = sub_cfgs.iter().all(|sub_cfg| {
466 matches!(
467 sub_cfg,
468 CfgEntry::NameValue { name: sym::target_feature, value: Some(_), .. }
469 )
470 });
471
472 if all_crate_features {
473 fmt.write_str("crate features ")?;
474 true
475 } else if all_target_features {
476 fmt.write_str("target features ")?;
477 true
478 } else {
479 false
480 }
481 };
482
483 fmt::from_fn(|f| {
484 sub_cfgs
485 .iter()
486 .map(|sub_cfg| {
487 if let CfgEntry::NameValue { value: Some(feat), .. } = sub_cfg
488 && short_longhand
489 {
490 Either::Left(self.code_wrappers().wrap(feat))
491 } else {
492 Either::Right(
493 Wrapped::with_parens()
494 .when(is_any_cfg(sub_cfg))
495 .wrap(Display(sub_cfg, self.1)),
496 )
497 }
498 })
499 .joined(separator, f)
500 })
501 .fmt(fmt)?;
502
503 Ok(())
504 }
505}
506
507impl fmt::Display for Display<'_> {
508 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
509 match &self.0 {
510 CfgEntry::Not(CfgEntry::Any(sub_cfgs, _), _) => {
511 let separator = if sub_cfgs.iter().all(is_simple_cfg) { " nor " } else { ", nor " };
512 fmt.write_str("neither ")?;
513
514 sub_cfgs
515 .iter()
516 .map(|sub_cfg| {
517 Wrapped::with_parens()
518 .when(is_any_cfg(sub_cfg))
519 .wrap(Display(sub_cfg, self.1))
520 })
521 .joined(separator, fmt)
522 }
523 CfgEntry::Not(simple @ CfgEntry::NameValue { .. }, _) => {
524 write!(fmt, "non-{}", Display(simple, self.1))
525 }
526 CfgEntry::Not(c, _) => write!(fmt, "not ({})", Display(c, self.1)),
527
528 CfgEntry::Any(sub_cfgs, _) => {
529 let separator = if sub_cfgs.iter().all(is_simple_cfg) { " or " } else { ", or " };
530 self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), separator)
531 }
532 CfgEntry::All(sub_cfgs, _) => self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), " and "),
533
534 CfgEntry::Bool(v, _) => {
535 if *v {
536 fmt.write_str("everywhere")
537 } else {
538 fmt.write_str("nowhere")
539 }
540 }
541
542 &CfgEntry::NameValue { name, value, .. } => {
543 let human_readable = match (*name, value) {
544 (sym::unix, None) => "Unix",
545 (sym::windows, None) => "Windows",
546 (sym::debug_assertions, None) => "debug-assertions enabled",
547 (sym::target_object_format, Some(format)) => match self.1 {
548 Format::LongHtml => {
549 return write!(fmt, "object format <code>{format}</code>");
550 }
551 Format::LongPlain => return write!(fmt, "object format `{format}`"),
552 Format::ShortHtml => return write!(fmt, "<code>{format}</code>"),
553 },
554 (sym::target_os, Some(os)) => human_readable_target_os(*os).unwrap_or_default(),
555 (sym::target_arch, Some(arch)) => {
556 human_readable_target_arch(*arch).unwrap_or_default()
557 }
558 (sym::target_vendor, Some(vendor)) => match vendor.as_str() {
559 "apple" => "Apple",
560 "pc" => "PC",
561 "sun" => "Sun",
562 "fortanix" => "Fortanix",
563 _ => "",
564 },
565 (sym::target_env, Some(env)) => {
566 human_readable_target_env(*env).unwrap_or_default()
567 }
568 (sym::target_endian, Some(endian)) => {
569 return write!(fmt, "{endian}-endian");
570 }
571 (sym::target_pointer_width, Some(bits)) => {
572 return write!(fmt, "{bits}-bit");
573 }
574 (sym::target_feature, Some(feat)) => match self.1 {
575 Format::LongHtml => {
576 return write!(fmt, "target feature <code>{feat}</code>");
577 }
578 Format::LongPlain => return write!(fmt, "target feature `{feat}`"),
579 Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
580 },
581 (sym::feature, Some(feat)) => match self.1 {
582 Format::LongHtml => {
583 return write!(fmt, "crate feature <code>{feat}</code>");
584 }
585 Format::LongPlain => return write!(fmt, "crate feature `{feat}`"),
586 Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
587 },
588 _ => "",
589 };
590 if !human_readable.is_empty() {
591 fmt.write_str(human_readable)
592 } else {
593 let value = value
594 .map(|v| fmt::from_fn(move |f| write!(f, "={}", self.1.escape(v.as_str()))))
595 .maybe_display();
596 self.code_wrappers()
597 .wrap(format_args!("{}{value}", self.1.escape(name.as_str())))
598 .fmt(fmt)
599 }
600 }
601
602 CfgEntry::Version(..) => {
603 Ok(())
605 }
606 }
607 }
608}
609
610fn human_readable_target_os(os: Symbol) -> Option<&'static str> {
611 let os = spec::Os::from_str(os.as_str()).ok()?;
612
613 use spec::Os::*;
614 Some(match os {
615 Aix => "AIX",
617 AmdHsa => "AMD HSA",
618 Android => "Android",
619 Cuda => "CUDA",
620 Cygwin => "Cygwin",
621 Dragonfly => "DragonFly BSD",
622 Emscripten => "Emscripten",
623 EspIdf => "ESP-IDF",
624 FreeBsd => "FreeBSD",
625 Fuchsia => "Fuchsia",
626 Haiku => "Haiku",
627 HelenOs => "HelenOS",
628 Hermit => "Hermit",
629 Horizon => "Horizon",
630 Hurd => "GNU/Hurd",
631 IOs => "iOS",
632 Illumos => "illumos",
633 L4Re => "L4Re",
634 Linux => "Linux",
635 LynxOs178 => "LynxOS-178",
636 MacOs => "macOS",
637 Managarm => "Managarm",
638 Motor => "Motor OS",
639 NetBsd => "NetBSD",
640 None => "bare-metal",
641 Nto => "QNX SDP 7.x",
642 NuttX => "NuttX",
643 OpenBsd => "OpenBSD",
644 Psp => "Play Station Portable",
645 Psx => "Play Station 1",
646 Qnx => "QNX SDP 8.0+",
647 Qurt => "QuRT",
648 Redox => "Redox OS",
649 Rtems => "RTEMS OS",
650 Solaris => "Solaris",
651 SolidAsp3 => "SOLID ASP3",
652 TeeOs => "TEEOS",
653 Trusty => "Trusty",
654 TvOs => "tvOS",
655 Uefi => "UEFI",
656 VexOs => "VEXos",
657 VisionOs => "visionOS",
658 Vita => "Play Station Vita",
659 VxWorks => "VxWorks",
660 Wasi => "WASI",
661 WatchOs => "watchOS",
662 Windows => "Windows",
663 Xous => "Xous",
664 Zkvm => "zero knowledge Virtual Machine",
665 Unknown | Other(_) => return Option::None,
667 })
668}
669
670fn human_readable_target_arch(os: Symbol) -> Option<&'static str> {
671 let arch = spec::Arch::from_str(os.as_str()).ok()?;
672
673 use spec::Arch::*;
674 Some(match arch {
675 AArch64 => "AArch64",
677 AmdGpu => "AMD GPU",
678 Arm => "ARM",
679 Arm64EC => "ARM64EC",
680 Avr => "AVR",
681 Bpf => "BPF",
682 CSky => "C-SKY",
683 Hexagon => "Hexagon",
684 LoongArch32 => "LoongArch32",
685 LoongArch64 => "LoongArch64",
686 M68k => "Motorola 680x0",
687 Mips => "MIPS",
688 Mips32r6 => "MIPS release 6",
689 Mips64 => "MIPS-64",
690 Mips64r6 => "MIPS-64 release 6",
691 Msp430 => "MSP430",
692 Nvptx64 => "NVidia GPU",
693 PowerPC => "PowerPC",
694 PowerPC64 => "PowerPC64",
695 RiscV32 => "RISC-V RV32",
696 RiscV64 => "RISC-V RV64",
697 S390x => "s390x",
698 Sparc => "SPARC",
699 Sparc64 => "SPARC-64",
700 SpirV => "SPIR-V",
701 Wasm32 | Wasm64 => "WebAssembly",
702 X86 => "x86",
703 X86_64 => "x86-64",
704 Xtensa => "Xtensa",
705 Other(_) => return None,
707 })
708}
709
710fn human_readable_target_env(env: Symbol) -> Option<&'static str> {
711 let env = spec::Env::from_str(env.as_str()).ok()?;
712
713 use spec::Env::*;
714 Some(match env {
715 Gnu => "GNU",
717 MacAbi => "Catalyst",
718 Mlibc => "Managarm C Library",
719 Msvc => "MSVC",
720 Musl => "musl",
721 Newlib => "Newlib",
722 Nto70 => "QNX SDP 7.0",
723 Nto71 => "QNX SDP 7.1",
724 Nto71IoSock => "QNX SDP 7.1 with io-sock",
725 Ohos => "OpenHarmony",
726 P1 => "WASIp1",
727 P2 => "WASIp2",
728 P3 => "WASIp3",
729 Relibc => "relibc",
730 Sgx => "SGX",
731 Sim => "Simulator",
732 Uclibc => "uClibc",
733 V5 => "V5",
734 Unspecified | Other(_) => return None,
736 })
737}
738
739#[derive(Clone, Debug)]
741pub(crate) struct CfgInfo {
742 hidden_cfg: FxHashMap<Symbol, DocCfgHide>,
745 current_cfg: Cfg,
748 auto_cfg_active: bool,
750 parent_is_doc_cfg: bool,
754}
755
756impl Default for CfgInfo {
757 fn default() -> Self {
758 Self {
759 hidden_cfg: FxHashMap::from_iter([
760 (sym::test, DocCfgHide::new()),
761 (sym::doc, DocCfgHide::new()),
762 (sym::doctest, DocCfgHide::new()),
763 ]),
764 current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)),
765 auto_cfg_active: true,
766 parent_is_doc_cfg: false,
767 }
768 }
769}
770
771fn handle_auto_cfg_hide_show(cfg_info: &mut CfgInfo, attr: &CfgHideShow) {
776 for (cfg_name, value) in &attr.values {
777 if attr.kind == HideOrShow::Show {
778 cfg_info
779 .hidden_cfg
780 .entry(*cfg_name)
781 .and_modify(|entry| entry.remove(value))
782 .or_insert_with(|| value.into());
783 } else {
784 cfg_info
785 .hidden_cfg
786 .entry(*cfg_name)
787 .and_modify(|entry| entry.merge_with(value))
788 .or_insert_with(|| value.into());
789 }
790 }
791}
792
793pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>(
794 attrs: I,
795 tcx: TyCtxt<'_>,
796 cfg_info: &mut CfgInfo,
797) -> Option<Arc<Cfg>> {
798 fn check_changed_auto_active_status(
799 changed_auto_active_status: &mut Option<rustc_span::Span>,
800 attr_span: Span,
801 cfg_info: &mut CfgInfo,
802 tcx: TyCtxt<'_>,
803 new_value: bool,
804 ) -> bool {
805 if let Some(first_change) = changed_auto_active_status {
806 if cfg_info.auto_cfg_active != new_value {
807 tcx.sess
808 .dcx()
809 .struct_span_err(
810 vec![*first_change, attr_span],
811 "`auto_cfg` was disabled and enabled more than once on the same item",
812 )
813 .emit();
814 return true;
815 }
816 } else {
817 *changed_auto_active_status = Some(attr_span);
818 }
819 cfg_info.auto_cfg_active = new_value;
820 false
821 }
822
823 let mut doc_cfg = attrs
824 .clone()
825 .filter_map(|attr| match attr {
826 Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => Some(d),
827 _ => None,
828 })
829 .peekable();
830 if doc_cfg.peek().is_some() {
832 if !cfg_info.parent_is_doc_cfg {
834 cfg_info.current_cfg = Cfg(CfgEntry::Bool(true, DUMMY_SP));
835 cfg_info.parent_is_doc_cfg = true;
836 }
837 for attr in doc_cfg {
838 for new_cfg in attr.cfg.clone() {
839 cfg_info.current_cfg &= Cfg(new_cfg);
840 }
841 }
842 } else {
843 cfg_info.parent_is_doc_cfg = false;
844 }
845
846 let mut changed_auto_active_status = None;
847
848 for attr in attrs {
850 if let Attribute::Parsed(AttributeKind::Doc(d)) = attr {
851 for (new_value, span) in &d.auto_cfg_change {
852 if check_changed_auto_active_status(
853 &mut changed_auto_active_status,
854 *span,
855 cfg_info,
856 tcx,
857 *new_value,
858 ) {
859 return None;
860 }
861 }
862 if let Some((_, span)) = d.auto_cfg.first() {
863 if check_changed_auto_active_status(
864 &mut changed_auto_active_status,
865 *span,
866 cfg_info,
867 tcx,
868 true,
869 ) {
870 return None;
871 }
872 for (value, _) in &d.auto_cfg {
873 handle_auto_cfg_hide_show(cfg_info, value);
874 }
875 }
876 } else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr {
877 for (feature, _) in features {
880 cfg_info.current_cfg &= Cfg(CfgEntry::NameValue {
881 name: sym::target_feature,
882 value: Some(*feature),
883 span: DUMMY_SP,
884 });
885 }
886 continue;
887 } else if !cfg_info.parent_is_doc_cfg
888 && let hir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr
889 {
890 for (new_cfg, _) in cfgs {
891 cfg_info.current_cfg &= Cfg(new_cfg.clone());
892 }
893 }
894 }
895
896 if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg {
899 None
900 } else if cfg_info.parent_is_doc_cfg {
901 if matches!(cfg_info.current_cfg.0, CfgEntry::Bool(true, _)) {
902 None
903 } else {
904 let mut cfg = cfg_info.current_cfg.clone();
905 cfg.sort_for_rendering();
906 Some(Arc::new(cfg))
907 }
908 } else {
909 match strip_hidden(&cfg_info.current_cfg.0, &cfg_info.hidden_cfg) {
912 None | Some(CfgEntry::Bool(true, _)) => None,
913 Some(cfg_entry) => {
914 let mut cfg = Cfg(cfg_entry);
915 cfg.sort_for_rendering();
916 Some(Arc::new(cfg))
917 }
918 }
919 }
920}