]> git.proxmox.com Git - rustc.git/blob - src/librustdoc/clean/cfg.rs
New upstream version 1.67.1+dfsg1
[rustc.git] / src / librustdoc / clean / cfg.rs
1 //! The representation of a `#[doc(cfg(...))]` attribute.
2
3 // FIXME: Once the portability lint RFC is implemented (see tracking issue #41619),
4 // switch to use those structures instead.
5
6 use std::fmt::{self, Write};
7 use std::mem;
8 use std::ops;
9
10 use rustc_ast::{LitKind, MetaItem, MetaItemKind, NestedMetaItem};
11 use rustc_data_structures::fx::FxHashSet;
12 use rustc_feature::Features;
13 use rustc_session::parse::ParseSess;
14 use rustc_span::symbol::{sym, Symbol};
15
16 use rustc_span::Span;
17
18 use crate::html::escape::Escape;
19
20 #[cfg(test)]
21 mod tests;
22
23 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
24 pub(crate) enum Cfg {
25 /// Accepts all configurations.
26 True,
27 /// Denies all configurations.
28 False,
29 /// A generic configuration option, e.g., `test` or `target_os = "linux"`.
30 Cfg(Symbol, Option<Symbol>),
31 /// Negates a configuration requirement, i.e., `not(x)`.
32 Not(Box<Cfg>),
33 /// Union of a list of configuration requirements, i.e., `any(...)`.
34 Any(Vec<Cfg>),
35 /// Intersection of a list of configuration requirements, i.e., `all(...)`.
36 All(Vec<Cfg>),
37 }
38
39 #[derive(PartialEq, Debug)]
40 pub(crate) struct InvalidCfgError {
41 pub(crate) msg: &'static str,
42 pub(crate) span: Span,
43 }
44
45 impl Cfg {
46 /// Parses a `NestedMetaItem` into a `Cfg`.
47 fn parse_nested(
48 nested_cfg: &NestedMetaItem,
49 exclude: &FxHashSet<Cfg>,
50 ) -> Result<Option<Cfg>, InvalidCfgError> {
51 match nested_cfg {
52 NestedMetaItem::MetaItem(ref cfg) => Cfg::parse_without(cfg, exclude),
53 NestedMetaItem::Lit(ref lit) => {
54 Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
55 }
56 }
57 }
58
59 pub(crate) fn parse_without(
60 cfg: &MetaItem,
61 exclude: &FxHashSet<Cfg>,
62 ) -> Result<Option<Cfg>, InvalidCfgError> {
63 let name = match cfg.ident() {
64 Some(ident) => ident.name,
65 None => {
66 return Err(InvalidCfgError {
67 msg: "expected a single identifier",
68 span: cfg.span,
69 });
70 }
71 };
72 match cfg.kind {
73 MetaItemKind::Word => {
74 let cfg = Cfg::Cfg(name, None);
75 if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
76 }
77 MetaItemKind::NameValue(ref lit) => match lit.kind {
78 LitKind::Str(value, _) => {
79 let cfg = Cfg::Cfg(name, Some(value));
80 if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
81 }
82 _ => Err(InvalidCfgError {
83 // FIXME: if the main #[cfg] syntax decided to support non-string literals,
84 // this should be changed as well.
85 msg: "value of cfg option should be a string literal",
86 span: lit.span,
87 }),
88 },
89 MetaItemKind::List(ref items) => {
90 let orig_len = items.len();
91 let sub_cfgs =
92 items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose());
93 let ret = match name {
94 sym::all => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)),
95 sym::any => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)),
96 sym::not => {
97 if orig_len == 1 {
98 let mut sub_cfgs = sub_cfgs.collect::<Vec<_>>();
99 if sub_cfgs.len() == 1 {
100 Ok(!sub_cfgs.pop().unwrap()?)
101 } else {
102 return Ok(None);
103 }
104 } else {
105 Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span })
106 }
107 }
108 _ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }),
109 };
110 match ret {
111 Ok(c) => Ok(Some(c)),
112 Err(e) => Err(e),
113 }
114 }
115 }
116 }
117
118 /// Parses a `MetaItem` into a `Cfg`.
119 ///
120 /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or
121 /// `target_os = "redox"`.
122 ///
123 /// If the content is not properly formatted, it will return an error indicating what and where
124 /// the error is.
125 pub(crate) fn parse(cfg: &MetaItem) -> Result<Cfg, InvalidCfgError> {
126 Self::parse_without(cfg, &FxHashSet::default()).map(|ret| ret.unwrap())
127 }
128
129 /// Checks whether the given configuration can be matched in the current session.
130 ///
131 /// Equivalent to `attr::cfg_matches`.
132 // FIXME: Actually make use of `features`.
133 pub(crate) fn matches(&self, parse_sess: &ParseSess, features: Option<&Features>) -> bool {
134 match *self {
135 Cfg::False => false,
136 Cfg::True => true,
137 Cfg::Not(ref child) => !child.matches(parse_sess, features),
138 Cfg::All(ref sub_cfgs) => {
139 sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(parse_sess, features))
140 }
141 Cfg::Any(ref sub_cfgs) => {
142 sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(parse_sess, features))
143 }
144 Cfg::Cfg(name, value) => parse_sess.config.contains(&(name, value)),
145 }
146 }
147
148 /// Whether the configuration consists of just `Cfg` or `Not`.
149 fn is_simple(&self) -> bool {
150 match *self {
151 Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
152 Cfg::All(..) | Cfg::Any(..) => false,
153 }
154 }
155
156 /// Whether the configuration consists of just `Cfg`, `Not` or `All`.
157 fn is_all(&self) -> bool {
158 match *self {
159 Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
160 Cfg::Any(..) => false,
161 }
162 }
163
164 /// Renders the configuration for human display, as a short HTML description.
165 pub(crate) fn render_short_html(&self) -> String {
166 let mut msg = Display(self, Format::ShortHtml).to_string();
167 if self.should_capitalize_first_letter() {
168 if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
169 msg[i..i + 1].make_ascii_uppercase();
170 }
171 }
172 msg
173 }
174
175 /// Renders the configuration for long display, as a long HTML description.
176 pub(crate) fn render_long_html(&self) -> String {
177 let on = if self.should_use_with_in_description() { "with" } else { "on" };
178
179 let mut msg =
180 format!("Available {on} <strong>{}</strong>", Display(self, Format::LongHtml));
181 if self.should_append_only_to_description() {
182 msg.push_str(" only");
183 }
184 msg.push('.');
185 msg
186 }
187
188 /// Renders the configuration for long display, as a long plain text description.
189 pub(crate) fn render_long_plain(&self) -> String {
190 let on = if self.should_use_with_in_description() { "with" } else { "on" };
191
192 let mut msg = format!("Available {on} {}", Display(self, Format::LongPlain));
193 if self.should_append_only_to_description() {
194 msg.push_str(" only");
195 }
196 msg
197 }
198
199 fn should_capitalize_first_letter(&self) -> bool {
200 match *self {
201 Cfg::False | Cfg::True | Cfg::Not(..) => true,
202 Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
203 sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
204 }
205 Cfg::Cfg(name, _) => name == sym::debug_assertions || name == sym::target_endian,
206 }
207 }
208
209 fn should_append_only_to_description(&self) -> bool {
210 match *self {
211 Cfg::False | Cfg::True => false,
212 Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
213 Cfg::Not(box Cfg::Cfg(..)) => true,
214 Cfg::Not(..) => false,
215 }
216 }
217
218 fn should_use_with_in_description(&self) -> bool {
219 matches!(self, Cfg::Cfg(sym::target_feature, _))
220 }
221
222 /// Attempt to simplify this cfg by assuming that `assume` is already known to be true, will
223 /// return `None` if simplification managed to completely eliminate any requirements from this
224 /// `Cfg`.
225 ///
226 /// See `tests::test_simplify_with` for examples.
227 pub(crate) fn simplify_with(&self, assume: &Cfg) -> Option<Cfg> {
228 if self == assume {
229 return None;
230 }
231
232 if let Cfg::All(a) = self {
233 let mut sub_cfgs: Vec<Cfg> = if let Cfg::All(b) = assume {
234 a.iter().filter(|a| !b.contains(a)).cloned().collect()
235 } else {
236 a.iter().filter(|&a| a != assume).cloned().collect()
237 };
238 let len = sub_cfgs.len();
239 return match len {
240 0 => None,
241 1 => sub_cfgs.pop(),
242 _ => Some(Cfg::All(sub_cfgs)),
243 };
244 } else if let Cfg::All(b) = assume {
245 if b.contains(self) {
246 return None;
247 }
248 }
249
250 Some(self.clone())
251 }
252 }
253
254 impl ops::Not for Cfg {
255 type Output = Cfg;
256 fn not(self) -> Cfg {
257 match self {
258 Cfg::False => Cfg::True,
259 Cfg::True => Cfg::False,
260 Cfg::Not(cfg) => *cfg,
261 s => Cfg::Not(Box::new(s)),
262 }
263 }
264 }
265
266 impl ops::BitAndAssign for Cfg {
267 fn bitand_assign(&mut self, other: Cfg) {
268 match (self, other) {
269 (&mut Cfg::False, _) | (_, Cfg::True) => {}
270 (s, Cfg::False) => *s = Cfg::False,
271 (s @ &mut Cfg::True, b) => *s = b,
272 (&mut Cfg::All(ref mut a), Cfg::All(ref mut b)) => {
273 for c in b.drain(..) {
274 if !a.contains(&c) {
275 a.push(c);
276 }
277 }
278 }
279 (&mut Cfg::All(ref mut a), ref mut b) => {
280 if !a.contains(b) {
281 a.push(mem::replace(b, Cfg::True));
282 }
283 }
284 (s, Cfg::All(mut a)) => {
285 let b = mem::replace(s, Cfg::True);
286 if !a.contains(&b) {
287 a.push(b);
288 }
289 *s = Cfg::All(a);
290 }
291 (s, b) => {
292 if *s != b {
293 let a = mem::replace(s, Cfg::True);
294 *s = Cfg::All(vec![a, b]);
295 }
296 }
297 }
298 }
299 }
300
301 impl ops::BitAnd for Cfg {
302 type Output = Cfg;
303 fn bitand(mut self, other: Cfg) -> Cfg {
304 self &= other;
305 self
306 }
307 }
308
309 impl ops::BitOrAssign for Cfg {
310 fn bitor_assign(&mut self, other: Cfg) {
311 match (self, other) {
312 (Cfg::True, _) | (_, Cfg::False) | (_, Cfg::True) => {}
313 (s @ &mut Cfg::False, b) => *s = b,
314 (&mut Cfg::Any(ref mut a), Cfg::Any(ref mut b)) => {
315 for c in b.drain(..) {
316 if !a.contains(&c) {
317 a.push(c);
318 }
319 }
320 }
321 (&mut Cfg::Any(ref mut a), ref mut b) => {
322 if !a.contains(b) {
323 a.push(mem::replace(b, Cfg::True));
324 }
325 }
326 (s, Cfg::Any(mut a)) => {
327 let b = mem::replace(s, Cfg::True);
328 if !a.contains(&b) {
329 a.push(b);
330 }
331 *s = Cfg::Any(a);
332 }
333 (s, b) => {
334 if *s != b {
335 let a = mem::replace(s, Cfg::True);
336 *s = Cfg::Any(vec![a, b]);
337 }
338 }
339 }
340 }
341 }
342
343 impl ops::BitOr for Cfg {
344 type Output = Cfg;
345 fn bitor(mut self, other: Cfg) -> Cfg {
346 self |= other;
347 self
348 }
349 }
350
351 #[derive(Clone, Copy)]
352 enum Format {
353 LongHtml,
354 LongPlain,
355 ShortHtml,
356 }
357
358 impl Format {
359 fn is_long(self) -> bool {
360 match self {
361 Format::LongHtml | Format::LongPlain => true,
362 Format::ShortHtml => false,
363 }
364 }
365
366 fn is_html(self) -> bool {
367 match self {
368 Format::LongHtml | Format::ShortHtml => true,
369 Format::LongPlain => false,
370 }
371 }
372 }
373
374 /// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
375 struct Display<'a>(&'a Cfg, Format);
376
377 fn write_with_opt_paren<T: fmt::Display>(
378 fmt: &mut fmt::Formatter<'_>,
379 has_paren: bool,
380 obj: T,
381 ) -> fmt::Result {
382 if has_paren {
383 fmt.write_char('(')?;
384 }
385 obj.fmt(fmt)?;
386 if has_paren {
387 fmt.write_char(')')?;
388 }
389 Ok(())
390 }
391
392 impl<'a> fmt::Display for Display<'a> {
393 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
394 match *self.0 {
395 Cfg::Not(ref child) => match **child {
396 Cfg::Any(ref sub_cfgs) => {
397 let separator =
398 if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
399 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
400 fmt.write_str(if i == 0 { "neither " } else { separator })?;
401 write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
402 }
403 Ok(())
404 }
405 ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Display(simple, self.1)),
406 ref c => write!(fmt, "not ({})", Display(c, self.1)),
407 },
408
409 Cfg::Any(ref sub_cfgs) => {
410 let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };
411
412 let short_longhand = self.1.is_long() && {
413 let all_crate_features = sub_cfgs
414 .iter()
415 .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
416 let all_target_features = sub_cfgs
417 .iter()
418 .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
419
420 if all_crate_features {
421 fmt.write_str("crate features ")?;
422 true
423 } else if all_target_features {
424 fmt.write_str("target features ")?;
425 true
426 } else {
427 false
428 }
429 };
430
431 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
432 if i != 0 {
433 fmt.write_str(separator)?;
434 }
435 if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
436 if self.1.is_html() {
437 write!(fmt, "<code>{}</code>", feat)?;
438 } else {
439 write!(fmt, "`{}`", feat)?;
440 }
441 } else {
442 write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
443 }
444 }
445 Ok(())
446 }
447
448 Cfg::All(ref sub_cfgs) => {
449 let short_longhand = self.1.is_long() && {
450 let all_crate_features = sub_cfgs
451 .iter()
452 .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
453 let all_target_features = sub_cfgs
454 .iter()
455 .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
456
457 if all_crate_features {
458 fmt.write_str("crate features ")?;
459 true
460 } else if all_target_features {
461 fmt.write_str("target features ")?;
462 true
463 } else {
464 false
465 }
466 };
467
468 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
469 if i != 0 {
470 fmt.write_str(" and ")?;
471 }
472 if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
473 if self.1.is_html() {
474 write!(fmt, "<code>{}</code>", feat)?;
475 } else {
476 write!(fmt, "`{}`", feat)?;
477 }
478 } else {
479 write_with_opt_paren(fmt, !sub_cfg.is_simple(), Display(sub_cfg, self.1))?;
480 }
481 }
482 Ok(())
483 }
484
485 Cfg::True => fmt.write_str("everywhere"),
486 Cfg::False => fmt.write_str("nowhere"),
487
488 Cfg::Cfg(name, value) => {
489 let human_readable = match (name, value) {
490 (sym::unix, None) => "Unix",
491 (sym::windows, None) => "Windows",
492 (sym::debug_assertions, None) => "debug-assertions enabled",
493 (sym::target_os, Some(os)) => match os.as_str() {
494 "android" => "Android",
495 "dragonfly" => "DragonFly BSD",
496 "emscripten" => "Emscripten",
497 "freebsd" => "FreeBSD",
498 "fuchsia" => "Fuchsia",
499 "haiku" => "Haiku",
500 "hermit" => "HermitCore",
501 "illumos" => "illumos",
502 "ios" => "iOS",
503 "l4re" => "L4Re",
504 "linux" => "Linux",
505 "macos" => "macOS",
506 "netbsd" => "NetBSD",
507 "openbsd" => "OpenBSD",
508 "redox" => "Redox",
509 "solaris" => "Solaris",
510 "wasi" => "WASI",
511 "windows" => "Windows",
512 _ => "",
513 },
514 (sym::target_arch, Some(arch)) => match arch.as_str() {
515 "aarch64" => "AArch64",
516 "arm" => "ARM",
517 "asmjs" => "JavaScript",
518 "m68k" => "M68k",
519 "mips" => "MIPS",
520 "mips64" => "MIPS-64",
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, "{}-bit", bits),
551 (sym::target_feature, Some(feat)) => match self.1 {
552 Format::LongHtml => {
553 return write!(fmt, "target feature <code>{}</code>", feat);
554 }
555 Format::LongPlain => return write!(fmt, "target feature `{}`", feat),
556 Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
557 },
558 (sym::feature, Some(feat)) => match self.1 {
559 Format::LongHtml => {
560 return write!(fmt, "crate feature <code>{}</code>", feat);
561 }
562 Format::LongPlain => return write!(fmt, "crate feature `{}`", feat),
563 Format::ShortHtml => return write!(fmt, "<code>{}</code>", feat),
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 }