1use std::sync::Arc;
7use std::{fmt, mem, ops};
8
9use itertools::Either;
10use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
11use rustc_data_structures::fx::{FxHashMap, FxHashSet};
12use rustc_data_structures::thin_vec::{ThinVec, thin_vec};
13use rustc_hir as hir;
14use rustc_hir::Attribute;
15use rustc_hir::attrs::{self, AttributeKind, CfgEntry, CfgHideShow, HideOrShow};
16use rustc_middle::ty::TyCtxt;
17use rustc_span::symbol::{Symbol, sym};
18use rustc_span::{DUMMY_SP, Span};
19
20use crate::display::{Joined as _, MaybeDisplay, Wrapped};
21use crate::html::escape::Escape;
22
23#[cfg(test)]
24mod tests;
25
26#[derive(Clone, Debug, Hash)]
27#[cfg_attr(test, derive(PartialEq))]
30pub(crate) struct Cfg(CfgEntry);
31
32#[derive(PartialEq, Debug)]
33pub(crate) struct InvalidCfgError {
34 pub(crate) msg: &'static str,
35 pub(crate) span: Span,
36}
37
38fn is_simple_cfg(cfg: &CfgEntry) -> bool {
40 match cfg {
41 CfgEntry::Bool(..)
42 | CfgEntry::NameValue { .. }
43 | CfgEntry::Not(..)
44 | CfgEntry::Version(..) => true,
45 CfgEntry::All(..) | CfgEntry::Any(..) => false,
46 }
47}
48
49fn is_all_cfg(cfg: &CfgEntry) -> bool {
51 match cfg {
52 CfgEntry::Bool(..)
53 | CfgEntry::NameValue { .. }
54 | CfgEntry::Not(..)
55 | CfgEntry::Version(..)
56 | CfgEntry::All(..) => true,
57 CfgEntry::Any(..) => false,
58 }
59}
60
61fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashSet<NameValueCfg>) -> Option<CfgEntry> {
62 match cfg {
63 CfgEntry::Bool(..) => Some(cfg.clone()),
64 CfgEntry::NameValue { .. } => {
65 if !hidden.contains(&NameValueCfg::from(cfg)) {
66 Some(cfg.clone())
67 } else {
68 None
69 }
70 }
71 CfgEntry::Not(cfg, _) => {
72 if let Some(cfg) = strip_hidden(cfg, hidden) {
73 Some(CfgEntry::Not(Box::new(cfg), DUMMY_SP))
74 } else {
75 None
76 }
77 }
78 CfgEntry::Any(cfgs, _) => {
79 let cfgs =
80 cfgs.iter().filter_map(|cfg| strip_hidden(cfg, hidden)).collect::<ThinVec<_>>();
81 if cfgs.is_empty() { None } else { Some(CfgEntry::Any(cfgs, DUMMY_SP)) }
82 }
83 CfgEntry::All(cfgs, _) => {
84 let cfgs =
85 cfgs.iter().filter_map(|cfg| strip_hidden(cfg, hidden)).collect::<ThinVec<_>>();
86 if cfgs.is_empty() { None } else { Some(CfgEntry::All(cfgs, DUMMY_SP)) }
87 }
88 CfgEntry::Version(..) => {
89 Some(cfg.clone())
91 }
92 }
93}
94
95fn should_capitalize_first_letter(cfg: &CfgEntry) -> bool {
96 match cfg {
97 CfgEntry::Bool(..) | CfgEntry::Not(..) | CfgEntry::Version(..) => true,
98 CfgEntry::Any(sub_cfgs, _) | CfgEntry::All(sub_cfgs, _) => {
99 sub_cfgs.first().map(should_capitalize_first_letter).unwrap_or(false)
100 }
101 CfgEntry::NameValue { name, .. } => {
102 *name == sym::debug_assertions || *name == sym::target_endian
103 }
104 }
105}
106
107impl Cfg {
108 fn parse_nested(
110 nested_cfg: &MetaItemInner,
111 exclude: &FxHashSet<NameValueCfg>,
112 ) -> Result<Option<Cfg>, InvalidCfgError> {
113 match nested_cfg {
114 MetaItemInner::MetaItem(cfg) => Cfg::parse_without(cfg, exclude),
115 MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => {
116 Ok(Some(Cfg(CfgEntry::Bool(*b, DUMMY_SP))))
117 }
118 MetaItemInner::Lit(lit) => {
119 Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
120 }
121 }
122 }
123
124 fn parse_without(
125 cfg: &MetaItem,
126 exclude: &FxHashSet<NameValueCfg>,
127 ) -> Result<Option<Cfg>, InvalidCfgError> {
128 let name = match cfg.ident() {
129 Some(ident) => ident.name,
130 None => {
131 return Err(InvalidCfgError {
132 msg: "expected a single identifier",
133 span: cfg.span,
134 });
135 }
136 };
137 match cfg.kind {
138 MetaItemKind::Word => {
139 if exclude.contains(&NameValueCfg::new(name)) {
140 Ok(None)
141 } else {
142 Ok(Some(Cfg(CfgEntry::NameValue { name, value: None, span: DUMMY_SP })))
143 }
144 }
145 MetaItemKind::NameValue(ref lit) => match lit.kind {
146 LitKind::Str(value, _) => {
147 if exclude.contains(&NameValueCfg::new_value(name, value)) {
148 Ok(None)
149 } else {
150 Ok(Some(Cfg(CfgEntry::NameValue {
151 name,
152 value: Some(value),
153 span: DUMMY_SP,
154 })))
155 }
156 }
157 _ => Err(InvalidCfgError {
158 msg: "value of cfg option should be a string literal",
161 span: lit.span,
162 }),
163 },
164 MetaItemKind::List(ref items) => {
165 let orig_len = items.len();
166 let mut sub_cfgs =
167 items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose());
168 let ret = match name {
169 sym::all => {
170 sub_cfgs.try_fold(Cfg(CfgEntry::Bool(true, DUMMY_SP)), |x, y| Ok(x & y?))
171 }
172 sym::any => {
173 sub_cfgs.try_fold(Cfg(CfgEntry::Bool(false, DUMMY_SP)), |x, y| Ok(x | y?))
174 }
175 sym::not => {
176 if orig_len == 1 {
177 let mut sub_cfgs = sub_cfgs.collect::<Vec<_>>();
178 if sub_cfgs.len() == 1 {
179 Ok(!sub_cfgs.pop().unwrap()?)
180 } else {
181 return Ok(None);
182 }
183 } else {
184 Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span })
185 }
186 }
187 _ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }),
188 };
189 match ret {
190 Ok(c) => Ok(Some(c)),
191 Err(e) => Err(e),
192 }
193 }
194 }
195 }
196
197 pub(crate) fn parse(cfg: &MetaItemInner) -> Result<Cfg, InvalidCfgError> {
205 Self::parse_nested(cfg, &FxHashSet::default()).map(|ret| ret.unwrap())
206 }
207
208 pub(crate) fn render_short_html(&self) -> String {
210 let mut msg = Display(&self.0, Format::ShortHtml).to_string();
211 if should_capitalize_first_letter(&self.0)
212 && let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric())
213 {
214 msg[i..i + 1].make_ascii_uppercase();
215 }
216 msg
217 }
218
219 fn render_long_inner(&self, format: Format) -> String {
220 let on = if self.omit_preposition() {
221 " "
222 } else if self.should_use_with_in_description() {
223 " with "
224 } else {
225 " on "
226 };
227
228 let mut msg = if matches!(format, Format::LongHtml) {
229 format!("Available{on}<strong>{}</strong>", Display(&self.0, format))
230 } else {
231 format!("Available{on}{}", Display(&self.0, format))
232 };
233 if self.should_append_only_to_description() {
234 msg.push_str(" only");
235 }
236 msg
237 }
238
239 pub(crate) fn render_long_html(&self) -> String {
241 let mut msg = self.render_long_inner(Format::LongHtml);
242 msg.push('.');
243 msg
244 }
245
246 pub(crate) fn render_long_plain(&self) -> String {
248 self.render_long_inner(Format::LongPlain)
249 }
250
251 fn should_append_only_to_description(&self) -> bool {
252 match self.0 {
253 CfgEntry::Any(..)
254 | CfgEntry::All(..)
255 | CfgEntry::NameValue { .. }
256 | CfgEntry::Version(..)
257 | CfgEntry::Not(box CfgEntry::NameValue { .. }, _) => true,
258 CfgEntry::Not(..) | CfgEntry::Bool(..) => false,
259 }
260 }
261
262 fn should_use_with_in_description(&self) -> bool {
263 matches!(self.0, CfgEntry::NameValue { name, .. } if name == sym::target_feature)
264 }
265
266 pub(crate) fn simplify_with(&self, assume: &Self) -> Option<Self> {
272 if self.0.is_equivalent_to(&assume.0) {
273 None
274 } else if let CfgEntry::All(a, _) = &self.0 {
275 let mut sub_cfgs: ThinVec<CfgEntry> = if let CfgEntry::All(b, _) = &assume.0 {
276 a.iter().filter(|a| !b.iter().any(|b| a.is_equivalent_to(b))).cloned().collect()
277 } else {
278 a.iter().filter(|&a| !a.is_equivalent_to(&assume.0)).cloned().collect()
279 };
280 let len = sub_cfgs.len();
281 match len {
282 0 => None,
283 1 => sub_cfgs.pop().map(Cfg),
284 _ => Some(Cfg(CfgEntry::All(sub_cfgs, DUMMY_SP))),
285 }
286 } else if let CfgEntry::All(b, _) = &assume.0
287 && b.iter().any(|b| b.is_equivalent_to(&self.0))
288 {
289 None
290 } else {
291 Some(self.clone())
292 }
293 }
294
295 fn omit_preposition(&self) -> bool {
296 matches!(self.0, CfgEntry::Bool(..))
297 }
298
299 pub(crate) fn inner(&self) -> &CfgEntry {
300 &self.0
301 }
302}
303
304impl ops::Not for Cfg {
305 type Output = Cfg;
306 fn not(self) -> Cfg {
307 Cfg(match self.0 {
308 CfgEntry::Bool(v, s) => CfgEntry::Bool(!v, s),
309 CfgEntry::Not(cfg, _) => *cfg,
310 s => CfgEntry::Not(Box::new(s), DUMMY_SP),
311 })
312 }
313}
314
315impl ops::BitAndAssign for Cfg {
316 fn bitand_assign(&mut self, other: Cfg) {
317 match (&mut self.0, other.0) {
318 (CfgEntry::Bool(false, _), _) | (_, CfgEntry::Bool(true, _)) => {}
319 (s, CfgEntry::Bool(false, _)) => *s = CfgEntry::Bool(false, DUMMY_SP),
320 (s @ CfgEntry::Bool(true, _), b) => *s = b,
321 (CfgEntry::All(a, _), CfgEntry::All(ref mut b, _)) => {
322 for c in b.drain(..) {
323 if !a.iter().any(|a| a.is_equivalent_to(&c)) {
324 a.push(c);
325 }
326 }
327 }
328 (CfgEntry::All(a, _), ref mut b) => {
329 if !a.iter().any(|a| a.is_equivalent_to(b)) {
330 a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP)));
331 }
332 }
333 (s, CfgEntry::All(mut a, _)) => {
334 let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
335 if !a.iter().any(|a| a.is_equivalent_to(&b)) {
336 a.push(b);
337 }
338 *s = CfgEntry::All(a, DUMMY_SP);
339 }
340 (s, b) => {
341 if !s.is_equivalent_to(&b) {
342 let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
343 *s = CfgEntry::All(thin_vec![a, b], DUMMY_SP);
344 }
345 }
346 }
347 }
348}
349
350impl ops::BitAnd for Cfg {
351 type Output = Cfg;
352 fn bitand(mut self, other: Cfg) -> Cfg {
353 self &= other;
354 self
355 }
356}
357
358impl ops::BitOrAssign for Cfg {
359 fn bitor_assign(&mut self, other: Cfg) {
360 match (&mut self.0, other.0) {
361 (CfgEntry::Bool(true, _), _)
362 | (_, CfgEntry::Bool(false, _))
363 | (_, CfgEntry::Bool(true, _)) => {}
364 (s @ CfgEntry::Bool(false, _), b) => *s = b,
365 (CfgEntry::Any(a, _), CfgEntry::Any(ref mut b, _)) => {
366 for c in b.drain(..) {
367 if !a.iter().any(|a| a.is_equivalent_to(&c)) {
368 a.push(c);
369 }
370 }
371 }
372 (CfgEntry::Any(a, _), ref mut b) => {
373 if !a.iter().any(|a| a.is_equivalent_to(b)) {
374 a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP)));
375 }
376 }
377 (s, CfgEntry::Any(mut a, _)) => {
378 let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
379 if !a.iter().any(|a| a.is_equivalent_to(&b)) {
380 a.push(b);
381 }
382 *s = CfgEntry::Any(a, DUMMY_SP);
383 }
384 (s, b) => {
385 if !s.is_equivalent_to(&b) {
386 let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
387 *s = CfgEntry::Any(thin_vec![a, b], DUMMY_SP);
388 }
389 }
390 }
391 }
392}
393
394impl ops::BitOr for Cfg {
395 type Output = Cfg;
396 fn bitor(mut self, other: Cfg) -> Cfg {
397 self |= other;
398 self
399 }
400}
401
402#[derive(Clone, Copy)]
403enum Format {
404 LongHtml,
405 LongPlain,
406 ShortHtml,
407}
408
409impl Format {
410 fn is_long(self) -> bool {
411 match self {
412 Format::LongHtml | Format::LongPlain => true,
413 Format::ShortHtml => false,
414 }
415 }
416
417 fn is_html(self) -> bool {
418 match self {
419 Format::LongHtml | Format::ShortHtml => true,
420 Format::LongPlain => false,
421 }
422 }
423
424 fn escape(self, s: &str) -> impl fmt::Display {
425 if self.is_html() { Either::Left(Escape(s)) } else { Either::Right(s) }
426 }
427}
428
429struct Display<'a>(&'a CfgEntry, Format);
431
432impl Display<'_> {
433 fn code_wrappers(&self) -> Wrapped<&'static str> {
434 if self.1.is_html() { Wrapped::with("<code>", "</code>") } else { Wrapped::with("`", "`") }
435 }
436
437 fn display_sub_cfgs(
438 &self,
439 fmt: &mut fmt::Formatter<'_>,
440 sub_cfgs: &[CfgEntry],
441 separator: &str,
442 ) -> fmt::Result {
443 use fmt::Display as _;
444
445 let short_longhand = self.1.is_long() && {
446 let all_crate_features = sub_cfgs.iter().all(|sub_cfg| {
447 matches!(sub_cfg, CfgEntry::NameValue { name: sym::feature, value: Some(_), .. })
448 });
449 let all_target_features = sub_cfgs.iter().all(|sub_cfg| {
450 matches!(
451 sub_cfg,
452 CfgEntry::NameValue { name: sym::target_feature, value: Some(_), .. }
453 )
454 });
455
456 if all_crate_features {
457 fmt.write_str("crate features ")?;
458 true
459 } else if all_target_features {
460 fmt.write_str("target features ")?;
461 true
462 } else {
463 false
464 }
465 };
466
467 fmt::from_fn(|f| {
468 sub_cfgs
469 .iter()
470 .map(|sub_cfg| {
471 if let CfgEntry::NameValue { value: Some(feat), .. } = sub_cfg
472 && short_longhand
473 {
474 Either::Left(self.code_wrappers().wrap(feat))
475 } else {
476 Either::Right(
477 Wrapped::with_parens()
478 .when(!is_all_cfg(sub_cfg))
479 .wrap(Display(sub_cfg, self.1)),
480 )
481 }
482 })
483 .joined(separator, f)
484 })
485 .fmt(fmt)?;
486
487 Ok(())
488 }
489}
490
491impl fmt::Display for Display<'_> {
492 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
493 match &self.0 {
494 CfgEntry::Not(box CfgEntry::Any(sub_cfgs, _), _) => {
495 let separator = if sub_cfgs.iter().all(is_simple_cfg) { " nor " } else { ", nor " };
496 fmt.write_str("neither ")?;
497
498 sub_cfgs
499 .iter()
500 .map(|sub_cfg| {
501 Wrapped::with_parens()
502 .when(!is_all_cfg(sub_cfg))
503 .wrap(Display(sub_cfg, self.1))
504 })
505 .joined(separator, fmt)
506 }
507 CfgEntry::Not(box simple @ CfgEntry::NameValue { .. }, _) => {
508 write!(fmt, "non-{}", Display(simple, self.1))
509 }
510 CfgEntry::Not(box c, _) => write!(fmt, "not ({})", Display(c, self.1)),
511
512 CfgEntry::Any(sub_cfgs, _) => {
513 let separator = if sub_cfgs.iter().all(is_simple_cfg) { " or " } else { ", or " };
514 self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), separator)
515 }
516 CfgEntry::All(sub_cfgs, _) => self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), " and "),
517
518 CfgEntry::Bool(v, _) => {
519 if *v {
520 fmt.write_str("everywhere")
521 } else {
522 fmt.write_str("nowhere")
523 }
524 }
525
526 &CfgEntry::NameValue { name, value, .. } => {
527 let human_readable = match (*name, value) {
528 (sym::unix, None) => "Unix",
529 (sym::windows, None) => "Windows",
530 (sym::debug_assertions, None) => "debug-assertions enabled",
531 (sym::target_os, Some(os)) => match os.as_str() {
532 "android" => "Android",
533 "cygwin" => "Cygwin",
534 "dragonfly" => "DragonFly BSD",
535 "emscripten" => "Emscripten",
536 "freebsd" => "FreeBSD",
537 "fuchsia" => "Fuchsia",
538 "haiku" => "Haiku",
539 "hermit" => "HermitCore",
540 "illumos" => "illumos",
541 "ios" => "iOS",
542 "l4re" => "L4Re",
543 "linux" => "Linux",
544 "macos" => "macOS",
545 "netbsd" => "NetBSD",
546 "openbsd" => "OpenBSD",
547 "redox" => "Redox",
548 "solaris" => "Solaris",
549 "tvos" => "tvOS",
550 "wasi" => "WASI",
551 "watchos" => "watchOS",
552 "windows" => "Windows",
553 "visionos" => "visionOS",
554 _ => "",
555 },
556 (sym::target_arch, Some(arch)) => match arch.as_str() {
557 "aarch64" => "AArch64",
558 "arm" => "ARM",
559 "loongarch32" => "LoongArch LA32",
560 "loongarch64" => "LoongArch LA64",
561 "m68k" => "M68k",
562 "csky" => "CSKY",
563 "mips" => "MIPS",
564 "mips32r6" => "MIPS Release 6",
565 "mips64" => "MIPS-64",
566 "mips64r6" => "MIPS-64 Release 6",
567 "msp430" => "MSP430",
568 "powerpc" => "PowerPC",
569 "powerpc64" => "PowerPC-64",
570 "riscv32" => "RISC-V RV32",
571 "riscv64" => "RISC-V RV64",
572 "s390x" => "s390x",
573 "sparc64" => "SPARC64",
574 "wasm32" | "wasm64" => "WebAssembly",
575 "x86" => "x86",
576 "x86_64" => "x86-64",
577 _ => "",
578 },
579 (sym::target_vendor, Some(vendor)) => match vendor.as_str() {
580 "apple" => "Apple",
581 "pc" => "PC",
582 "sun" => "Sun",
583 "fortanix" => "Fortanix",
584 _ => "",
585 },
586 (sym::target_env, Some(env)) => match env.as_str() {
587 "gnu" => "GNU",
588 "msvc" => "MSVC",
589 "musl" => "musl",
590 "newlib" => "Newlib",
591 "uclibc" => "uClibc",
592 "sgx" => "SGX",
593 _ => "",
594 },
595 (sym::target_endian, Some(endian)) => {
596 return write!(fmt, "{endian}-endian");
597 }
598 (sym::target_pointer_width, Some(bits)) => {
599 return write!(fmt, "{bits}-bit");
600 }
601 (sym::target_feature, Some(feat)) => match self.1 {
602 Format::LongHtml => {
603 return write!(fmt, "target feature <code>{feat}</code>");
604 }
605 Format::LongPlain => return write!(fmt, "target feature `{feat}`"),
606 Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
607 },
608 (sym::feature, Some(feat)) => match self.1 {
609 Format::LongHtml => {
610 return write!(fmt, "crate feature <code>{feat}</code>");
611 }
612 Format::LongPlain => return write!(fmt, "crate feature `{feat}`"),
613 Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
614 },
615 _ => "",
616 };
617 if !human_readable.is_empty() {
618 fmt.write_str(human_readable)
619 } else {
620 let value = value
621 .map(|v| fmt::from_fn(move |f| write!(f, "={}", self.1.escape(v.as_str()))))
622 .maybe_display();
623 self.code_wrappers()
624 .wrap(format_args!("{}{value}", self.1.escape(name.as_str())))
625 .fmt(fmt)
626 }
627 }
628
629 CfgEntry::Version(..) => {
630 Ok(())
632 }
633 }
634 }
635}
636
637#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
638struct NameValueCfg {
639 name: Symbol,
640 value: Option<Symbol>,
641}
642
643impl NameValueCfg {
644 fn new(name: Symbol) -> Self {
645 Self { name, value: None }
646 }
647
648 fn new_value(name: Symbol, value: Symbol) -> Self {
649 Self { name, value: Some(value) }
650 }
651}
652
653impl<'a> From<&'a CfgEntry> for NameValueCfg {
654 fn from(cfg: &'a CfgEntry) -> Self {
655 match cfg {
656 CfgEntry::NameValue { name, value, .. } => NameValueCfg { name: *name, value: *value },
657 _ => NameValueCfg { name: sym::empty, value: None },
658 }
659 }
660}
661
662impl<'a> From<&'a attrs::CfgInfo> for NameValueCfg {
663 fn from(cfg: &'a attrs::CfgInfo) -> Self {
664 Self { name: cfg.name, value: cfg.value.map(|(value, _)| value) }
665 }
666}
667
668#[derive(Clone, Debug)]
670pub(crate) struct CfgInfo {
671 hidden_cfg: FxHashSet<NameValueCfg>,
674 current_cfg: Cfg,
677 auto_cfg_active: bool,
679 parent_is_doc_cfg: bool,
683}
684
685impl Default for CfgInfo {
686 fn default() -> Self {
687 Self {
688 hidden_cfg: FxHashSet::from_iter([
689 NameValueCfg::new(sym::test),
690 NameValueCfg::new(sym::doc),
691 NameValueCfg::new(sym::doctest),
692 ]),
693 current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)),
694 auto_cfg_active: true,
695 parent_is_doc_cfg: false,
696 }
697 }
698}
699
700fn show_hide_show_conflict_error(
701 tcx: TyCtxt<'_>,
702 item_span: rustc_span::Span,
703 previous: rustc_span::Span,
704) {
705 let mut diag = tcx.sess.dcx().struct_span_err(
706 item_span,
707 format!(
708 "same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item"
709 ),
710 );
711 diag.span_note(previous, "first change was here");
712 diag.emit();
713}
714
715fn handle_auto_cfg_hide_show(
723 tcx: TyCtxt<'_>,
724 cfg_info: &mut CfgInfo,
725 attr: &CfgHideShow,
726 new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
727 new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
728) {
729 for value in &attr.values {
730 let simple = NameValueCfg::from(value);
731 if attr.kind == HideOrShow::Show {
732 if let Some(span) = new_hide_attrs.get(&(simple.name, simple.value)) {
733 show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span);
734 } else {
735 new_show_attrs.insert((simple.name, simple.value), value.span_for_name_and_value());
736 }
737 cfg_info.hidden_cfg.remove(&simple);
738 } else {
739 if let Some(span) = new_show_attrs.get(&(simple.name, simple.value)) {
740 show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span);
741 } else {
742 new_hide_attrs.insert((simple.name, simple.value), value.span_for_name_and_value());
743 }
744 cfg_info.hidden_cfg.insert(simple);
745 }
746 }
747}
748
749pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>(
750 attrs: I,
751 tcx: TyCtxt<'_>,
752 cfg_info: &mut CfgInfo,
753) -> Option<Arc<Cfg>> {
754 fn single<T: IntoIterator>(it: T) -> Option<T::Item> {
755 let mut iter = it.into_iter();
756 let item = iter.next()?;
757 if iter.next().is_some() {
758 return None;
759 }
760 Some(item)
761 }
762
763 fn check_changed_auto_active_status(
764 changed_auto_active_status: &mut Option<rustc_span::Span>,
765 attr_span: Span,
766 cfg_info: &mut CfgInfo,
767 tcx: TyCtxt<'_>,
768 new_value: bool,
769 ) -> bool {
770 if let Some(first_change) = changed_auto_active_status {
771 if cfg_info.auto_cfg_active != new_value {
772 tcx.sess
773 .dcx()
774 .struct_span_err(
775 vec![*first_change, attr_span],
776 "`auto_cfg` was disabled and enabled more than once on the same item",
777 )
778 .emit();
779 return true;
780 }
781 } else {
782 *changed_auto_active_status = Some(attr_span);
783 }
784 cfg_info.auto_cfg_active = new_value;
785 false
786 }
787
788 let mut new_show_attrs = FxHashMap::default();
789 let mut new_hide_attrs = FxHashMap::default();
790
791 let mut doc_cfg = attrs
792 .clone()
793 .filter_map(|attr| match attr {
794 Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => Some(d),
795 _ => None,
796 })
797 .peekable();
798 if doc_cfg.peek().is_some() {
800 if !cfg_info.parent_is_doc_cfg {
802 cfg_info.current_cfg = Cfg(CfgEntry::Bool(true, DUMMY_SP));
803 cfg_info.parent_is_doc_cfg = true;
804 }
805 for attr in doc_cfg {
806 for new_cfg in attr.cfg.clone() {
807 cfg_info.current_cfg &= Cfg(new_cfg);
808 }
809 }
810 } else {
811 cfg_info.parent_is_doc_cfg = false;
812 }
813
814 let mut changed_auto_active_status = None;
815
816 for attr in attrs {
818 if let Attribute::Parsed(AttributeKind::Doc(d)) = attr {
819 for (new_value, span) in &d.auto_cfg_change {
820 if check_changed_auto_active_status(
821 &mut changed_auto_active_status,
822 *span,
823 cfg_info,
824 tcx,
825 *new_value,
826 ) {
827 return None;
828 }
829 }
830 if let Some((_, span)) = d.auto_cfg.first() {
831 if check_changed_auto_active_status(
832 &mut changed_auto_active_status,
833 *span,
834 cfg_info,
835 tcx,
836 true,
837 ) {
838 return None;
839 }
840 for (value, _) in &d.auto_cfg {
841 handle_auto_cfg_hide_show(
842 tcx,
843 cfg_info,
844 value,
845 &mut new_show_attrs,
846 &mut new_hide_attrs,
847 );
848 }
849 }
850 } else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr {
851 for (feature, _) in features {
854 cfg_info.current_cfg &= Cfg(CfgEntry::NameValue {
855 name: sym::target_feature,
856 value: Some(*feature),
857 span: DUMMY_SP,
858 });
859 }
860 continue;
861 } else if !cfg_info.parent_is_doc_cfg
862 && let Some(ident) = attr.ident()
863 && matches!(ident.name, sym::cfg | sym::cfg_trace)
864 && let Some(attr) = single(attr.meta_item_list()?)
865 && let Ok(new_cfg) = Cfg::parse(&attr)
866 {
867 cfg_info.current_cfg &= new_cfg;
868 }
869 }
870
871 if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg {
874 None
875 } else if cfg_info.parent_is_doc_cfg {
876 if matches!(cfg_info.current_cfg.0, CfgEntry::Bool(true, _)) {
877 None
878 } else {
879 Some(Arc::new(cfg_info.current_cfg.clone()))
880 }
881 } else {
882 match strip_hidden(&cfg_info.current_cfg.0, &cfg_info.hidden_cfg) {
885 None | Some(CfgEntry::Bool(true, _)) => None,
886 Some(cfg) => Some(Arc::new(Cfg(cfg))),
887 }
888 }
889}