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