1use std::fmt::{self, Write};
7use std::{mem, ops};
8
9use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
10use rustc_data_structures::fx::FxHashSet;
11use rustc_session::parse::ParseSess;
12use rustc_span::Span;
13use rustc_span::symbol::{Symbol, sym};
14
15use crate::display::Joined as _;
16use crate::html::escape::Escape;
17
18#[cfg(test)]
19mod tests;
20
21#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub(crate) enum Cfg {
23 True,
25 False,
27 Cfg(Symbol, Option<Symbol>),
29 Not(Box<Cfg>),
31 Any(Vec<Cfg>),
33 All(Vec<Cfg>),
35}
36
37#[derive(PartialEq, Debug)]
38pub(crate) struct InvalidCfgError {
39 pub(crate) msg: &'static str,
40 pub(crate) span: Span,
41}
42
43impl Cfg {
44 fn parse_nested(
46 nested_cfg: &MetaItemInner,
47 exclude: &FxHashSet<Cfg>,
48 ) -> Result<Option<Cfg>, InvalidCfgError> {
49 match nested_cfg {
50 MetaItemInner::MetaItem(cfg) => Cfg::parse_without(cfg, exclude),
51 MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => match *b {
52 true => Ok(Some(Cfg::True)),
53 false => Ok(Some(Cfg::False)),
54 },
55 MetaItemInner::Lit(lit) => {
56 Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
57 }
58 }
59 }
60
61 pub(crate) fn parse_without(
62 cfg: &MetaItem,
63 exclude: &FxHashSet<Cfg>,
64 ) -> Result<Option<Cfg>, InvalidCfgError> {
65 let name = match cfg.ident() {
66 Some(ident) => ident.name,
67 None => {
68 return Err(InvalidCfgError {
69 msg: "expected a single identifier",
70 span: cfg.span,
71 });
72 }
73 };
74 match cfg.kind {
75 MetaItemKind::Word => {
76 let cfg = Cfg::Cfg(name, None);
77 if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
78 }
79 MetaItemKind::NameValue(ref lit) => match lit.kind {
80 LitKind::Str(value, _) => {
81 let cfg = Cfg::Cfg(name, Some(value));
82 if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
83 }
84 _ => Err(InvalidCfgError {
85 msg: "value of cfg option should be a string literal",
88 span: lit.span,
89 }),
90 },
91 MetaItemKind::List(ref items) => {
92 let orig_len = items.len();
93 let mut sub_cfgs =
94 items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose());
95 let ret = match name {
96 sym::all => sub_cfgs.try_fold(Cfg::True, |x, y| Ok(x & y?)),
97 sym::any => sub_cfgs.try_fold(Cfg::False, |x, y| Ok(x | y?)),
98 sym::not => {
99 if orig_len == 1 {
100 let mut sub_cfgs = sub_cfgs.collect::<Vec<_>>();
101 if sub_cfgs.len() == 1 {
102 Ok(!sub_cfgs.pop().unwrap()?)
103 } else {
104 return Ok(None);
105 }
106 } else {
107 Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span })
108 }
109 }
110 _ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }),
111 };
112 match ret {
113 Ok(c) => Ok(Some(c)),
114 Err(e) => Err(e),
115 }
116 }
117 }
118 }
119
120 pub(crate) fn parse(cfg: &MetaItemInner) -> Result<Cfg, InvalidCfgError> {
128 Self::parse_nested(cfg, &FxHashSet::default()).map(|ret| ret.unwrap())
129 }
130
131 pub(crate) fn matches(&self, psess: &ParseSess) -> bool {
135 match *self {
136 Cfg::False => false,
137 Cfg::True => true,
138 Cfg::Not(ref child) => !child.matches(psess),
139 Cfg::All(ref sub_cfgs) => sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(psess)),
140 Cfg::Any(ref sub_cfgs) => sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(psess)),
141 Cfg::Cfg(name, value) => psess.config.contains(&(name, value)),
142 }
143 }
144
145 fn is_simple(&self) -> bool {
147 match self {
148 Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
149 Cfg::All(..) | Cfg::Any(..) => false,
150 }
151 }
152
153 fn is_all(&self) -> bool {
155 match self {
156 Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
157 Cfg::Any(..) => false,
158 }
159 }
160
161 pub(crate) fn render_short_html(&self) -> String {
163 let mut msg = Display(self, Format::ShortHtml).to_string();
164 if self.should_capitalize_first_letter()
165 && let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric())
166 {
167 msg[i..i + 1].make_ascii_uppercase();
168 }
169 msg
170 }
171
172 fn render_long_inner(&self, format: Format) -> String {
173 let on = if self.omit_preposition() {
174 " "
175 } else if self.should_use_with_in_description() {
176 " with "
177 } else {
178 " on "
179 };
180
181 let mut msg = if matches!(format, Format::LongHtml) {
182 format!("Available{on}<strong>{}</strong>", Display(self, format))
183 } else {
184 format!("Available{on}{}", Display(self, format))
185 };
186 if self.should_append_only_to_description() {
187 msg.push_str(" only");
188 }
189 msg
190 }
191
192 pub(crate) fn render_long_html(&self) -> String {
194 let mut msg = self.render_long_inner(Format::LongHtml);
195 msg.push('.');
196 msg
197 }
198
199 pub(crate) fn render_long_plain(&self) -> String {
201 self.render_long_inner(Format::LongPlain)
202 }
203
204 fn should_capitalize_first_letter(&self) -> bool {
205 match *self {
206 Cfg::False | Cfg::True | Cfg::Not(..) => true,
207 Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
208 sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
209 }
210 Cfg::Cfg(name, _) => name == sym::debug_assertions || name == sym::target_endian,
211 }
212 }
213
214 fn should_append_only_to_description(&self) -> bool {
215 match self {
216 Cfg::False | Cfg::True => false,
217 Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
218 Cfg::Not(box Cfg::Cfg(..)) => true,
219 Cfg::Not(..) => false,
220 }
221 }
222
223 fn should_use_with_in_description(&self) -> bool {
224 matches!(self, Cfg::Cfg(sym::target_feature, _))
225 }
226
227 pub(crate) fn simplify_with(&self, assume: &Self) -> Option<Self> {
233 if self == assume {
234 None
235 } else if let Cfg::All(a) = self {
236 let mut sub_cfgs: Vec<Cfg> = if let Cfg::All(b) = assume {
237 a.iter().filter(|a| !b.contains(a)).cloned().collect()
238 } else {
239 a.iter().filter(|&a| a != assume).cloned().collect()
240 };
241 let len = sub_cfgs.len();
242 match len {
243 0 => None,
244 1 => sub_cfgs.pop(),
245 _ => Some(Cfg::All(sub_cfgs)),
246 }
247 } else if let Cfg::All(b) = assume
248 && b.contains(self)
249 {
250 None
251 } else {
252 Some(self.clone())
253 }
254 }
255
256 fn omit_preposition(&self) -> bool {
257 matches!(self, Cfg::True | Cfg::False)
258 }
259}
260
261impl ops::Not for Cfg {
262 type Output = Cfg;
263 fn not(self) -> Cfg {
264 match self {
265 Cfg::False => Cfg::True,
266 Cfg::True => Cfg::False,
267 Cfg::Not(cfg) => *cfg,
268 s => Cfg::Not(Box::new(s)),
269 }
270 }
271}
272
273impl ops::BitAndAssign for Cfg {
274 fn bitand_assign(&mut self, other: Cfg) {
275 match (self, other) {
276 (Cfg::False, _) | (_, Cfg::True) => {}
277 (s, Cfg::False) => *s = Cfg::False,
278 (s @ Cfg::True, b) => *s = b,
279 (Cfg::All(a), Cfg::All(ref mut b)) => {
280 for c in b.drain(..) {
281 if !a.contains(&c) {
282 a.push(c);
283 }
284 }
285 }
286 (Cfg::All(a), ref mut b) => {
287 if !a.contains(b) {
288 a.push(mem::replace(b, Cfg::True));
289 }
290 }
291 (s, Cfg::All(mut a)) => {
292 let b = mem::replace(s, Cfg::True);
293 if !a.contains(&b) {
294 a.push(b);
295 }
296 *s = Cfg::All(a);
297 }
298 (s, b) => {
299 if *s != b {
300 let a = mem::replace(s, Cfg::True);
301 *s = Cfg::All(vec![a, b]);
302 }
303 }
304 }
305 }
306}
307
308impl ops::BitAnd for Cfg {
309 type Output = Cfg;
310 fn bitand(mut self, other: Cfg) -> Cfg {
311 self &= other;
312 self
313 }
314}
315
316impl ops::BitOrAssign for Cfg {
317 fn bitor_assign(&mut self, other: Cfg) {
318 match (self, other) {
319 (Cfg::True, _) | (_, Cfg::False) | (_, Cfg::True) => {}
320 (s @ Cfg::False, b) => *s = b,
321 (Cfg::Any(a), Cfg::Any(ref mut b)) => {
322 for c in b.drain(..) {
323 if !a.contains(&c) {
324 a.push(c);
325 }
326 }
327 }
328 (Cfg::Any(a), ref mut b) => {
329 if !a.contains(b) {
330 a.push(mem::replace(b, Cfg::True));
331 }
332 }
333 (s, Cfg::Any(mut a)) => {
334 let b = mem::replace(s, Cfg::True);
335 if !a.contains(&b) {
336 a.push(b);
337 }
338 *s = Cfg::Any(a);
339 }
340 (s, b) => {
341 if *s != b {
342 let a = mem::replace(s, Cfg::True);
343 *s = Cfg::Any(vec![a, b]);
344 }
345 }
346 }
347 }
348}
349
350impl ops::BitOr for Cfg {
351 type Output = Cfg;
352 fn bitor(mut self, other: Cfg) -> Cfg {
353 self |= other;
354 self
355 }
356}
357
358#[derive(Clone, Copy)]
359enum Format {
360 LongHtml,
361 LongPlain,
362 ShortHtml,
363}
364
365impl Format {
366 fn is_long(self) -> bool {
367 match self {
368 Format::LongHtml | Format::LongPlain => true,
369 Format::ShortHtml => false,
370 }
371 }
372
373 fn is_html(self) -> bool {
374 match self {
375 Format::LongHtml | Format::ShortHtml => true,
376 Format::LongPlain => false,
377 }
378 }
379}
380
381struct Display<'a>(&'a Cfg, Format);
383
384fn write_with_opt_paren<T: fmt::Display>(
385 fmt: &mut fmt::Formatter<'_>,
386 has_paren: bool,
387 obj: T,
388) -> fmt::Result {
389 if has_paren {
390 fmt.write_char('(')?;
391 }
392 obj.fmt(fmt)?;
393 if has_paren {
394 fmt.write_char(')')?;
395 }
396 Ok(())
397}
398
399impl Display<'_> {
400 fn display_sub_cfgs(
401 &self,
402 fmt: &mut fmt::Formatter<'_>,
403 sub_cfgs: &[Cfg],
404 separator: &str,
405 ) -> fmt::Result {
406 use fmt::Display as _;
407
408 let short_longhand = self.1.is_long() && {
409 let all_crate_features =
410 sub_cfgs.iter().all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
411 let all_target_features = sub_cfgs
412 .iter()
413 .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
414
415 if all_crate_features {
416 fmt.write_str("crate features ")?;
417 true
418 } else if all_target_features {
419 fmt.write_str("target features ")?;
420 true
421 } else {
422 false
423 }
424 };
425
426 fmt::from_fn(|f| {
427 sub_cfgs
428 .iter()
429 .map(|sub_cfg| {
430 fmt::from_fn(move |fmt| {
431 if let Cfg::Cfg(_, Some(feat)) = sub_cfg
432 && short_longhand
433 {
434 if self.1.is_html() {
435 write!(fmt, "<code>{feat}</code>")?;
436 } else {
437 write!(fmt, "`{feat}`")?;
438 }
439 } else {
440 write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
441 }
442 Ok(())
443 })
444 })
445 .joined(separator, f)
446 })
447 .fmt(fmt)?;
448
449 Ok(())
450 }
451}
452
453impl fmt::Display for Display<'_> {
454 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
455 match self.0 {
456 Cfg::Not(box Cfg::Any(sub_cfgs)) => {
457 let separator =
458 if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
459 fmt.write_str("neither ")?;
460
461 sub_cfgs
462 .iter()
463 .map(|sub_cfg| {
464 fmt::from_fn(|fmt| {
465 write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))
466 })
467 })
468 .joined(separator, fmt)
469 }
470 Cfg::Not(box simple @ Cfg::Cfg(..)) => write!(fmt, "non-{}", Display(simple, self.1)),
471 Cfg::Not(box c) => write!(fmt, "not ({})", Display(c, self.1)),
472
473 Cfg::Any(sub_cfgs) => {
474 let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };
475 self.display_sub_cfgs(fmt, sub_cfgs, separator)
476 }
477 Cfg::All(sub_cfgs) => self.display_sub_cfgs(fmt, sub_cfgs, " and "),
478
479 Cfg::True => fmt.write_str("everywhere"),
480 Cfg::False => fmt.write_str("nowhere"),
481
482 &Cfg::Cfg(name, value) => {
483 let human_readable = match (name, value) {
484 (sym::unix, None) => "Unix",
485 (sym::windows, None) => "Windows",
486 (sym::debug_assertions, None) => "debug-assertions enabled",
487 (sym::target_os, Some(os)) => match os.as_str() {
488 "android" => "Android",
489 "dragonfly" => "DragonFly BSD",
490 "emscripten" => "Emscripten",
491 "freebsd" => "FreeBSD",
492 "fuchsia" => "Fuchsia",
493 "haiku" => "Haiku",
494 "hermit" => "HermitCore",
495 "illumos" => "illumos",
496 "ios" => "iOS",
497 "l4re" => "L4Re",
498 "linux" => "Linux",
499 "macos" => "macOS",
500 "netbsd" => "NetBSD",
501 "openbsd" => "OpenBSD",
502 "redox" => "Redox",
503 "solaris" => "Solaris",
504 "tvos" => "tvOS",
505 "wasi" => "WASI",
506 "watchos" => "watchOS",
507 "windows" => "Windows",
508 "visionos" => "visionOS",
509 _ => "",
510 },
511 (sym::target_arch, Some(arch)) => match arch.as_str() {
512 "aarch64" => "AArch64",
513 "arm" => "ARM",
514 "loongarch32" => "LoongArch LA32",
515 "loongarch64" => "LoongArch LA64",
516 "m68k" => "M68k",
517 "csky" => "CSKY",
518 "mips" => "MIPS",
519 "mips32r6" => "MIPS Release 6",
520 "mips64" => "MIPS-64",
521 "mips64r6" => "MIPS-64 Release 6",
522 "msp430" => "MSP430",
523 "powerpc" => "PowerPC",
524 "powerpc64" => "PowerPC-64",
525 "riscv32" => "RISC-V RV32",
526 "riscv64" => "RISC-V RV64",
527 "s390x" => "s390x",
528 "sparc64" => "SPARC64",
529 "wasm32" | "wasm64" => "WebAssembly",
530 "x86" => "x86",
531 "x86_64" => "x86-64",
532 _ => "",
533 },
534 (sym::target_vendor, Some(vendor)) => match vendor.as_str() {
535 "apple" => "Apple",
536 "pc" => "PC",
537 "sun" => "Sun",
538 "fortanix" => "Fortanix",
539 _ => "",
540 },
541 (sym::target_env, Some(env)) => match env.as_str() {
542 "gnu" => "GNU",
543 "msvc" => "MSVC",
544 "musl" => "musl",
545 "newlib" => "Newlib",
546 "uclibc" => "uClibc",
547 "sgx" => "SGX",
548 _ => "",
549 },
550 (sym::target_endian, Some(endian)) => return write!(fmt, "{endian}-endian"),
551 (sym::target_pointer_width, Some(bits)) => return write!(fmt, "{bits}-bit"),
552 (sym::target_feature, Some(feat)) => match self.1 {
553 Format::LongHtml => {
554 return write!(fmt, "target feature <code>{feat}</code>");
555 }
556 Format::LongPlain => return write!(fmt, "target feature `{feat}`"),
557 Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
558 },
559 (sym::feature, Some(feat)) => match self.1 {
560 Format::LongHtml => {
561 return write!(fmt, "crate feature <code>{feat}</code>");
562 }
563 Format::LongPlain => return write!(fmt, "crate feature `{feat}`"),
564 Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
565 },
566 _ => "",
567 };
568 if !human_readable.is_empty() {
569 fmt.write_str(human_readable)
570 } else if let Some(v) = value {
571 if self.1.is_html() {
572 write!(
573 fmt,
574 r#"<code>{}="{}"</code>"#,
575 Escape(name.as_str()),
576 Escape(v.as_str())
577 )
578 } else {
579 write!(fmt, r#"`{name}="{v}"`"#)
580 }
581 } else if self.1.is_html() {
582 write!(fmt, "<code>{}</code>", Escape(name.as_str()))
583 } else {
584 write!(fmt, "`{name}`")
585 }
586 }
587 }
588 }
589}