]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/clean/cfg.rs
13df1892a5f1776305a98e2970613410be8bdc07
[rust.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_feature::Features;
11 use rustc_span::symbol::{sym, Symbol};
12 use syntax::ast::{LitKind, MetaItem, MetaItemKind, NestedMetaItem};
13 use syntax::sess::ParseSess;
14
15 use rustc_span::Span;
16
17 use crate::html::escape::Escape;
18
19 #[cfg(test)]
20 mod tests;
21
22 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
23 pub enum Cfg {
24     /// Accepts all configurations.
25     True,
26     /// Denies all configurations.
27     False,
28     /// A generic configuration option, e.g., `test` or `target_os = "linux"`.
29     Cfg(Symbol, Option<Symbol>),
30     /// Negates a configuration requirement, i.e., `not(x)`.
31     Not(Box<Cfg>),
32     /// Union of a list of configuration requirements, i.e., `any(...)`.
33     Any(Vec<Cfg>),
34     /// Intersection of a list of configuration requirements, i.e., `all(...)`.
35     All(Vec<Cfg>),
36 }
37
38 #[derive(PartialEq, Debug)]
39 pub struct InvalidCfgError {
40     pub msg: &'static str,
41     pub span: Span,
42 }
43
44 impl Cfg {
45     /// Parses a `NestedMetaItem` into a `Cfg`.
46     fn parse_nested(nested_cfg: &NestedMetaItem) -> Result<Cfg, InvalidCfgError> {
47         match nested_cfg {
48             NestedMetaItem::MetaItem(ref cfg) => Cfg::parse(cfg),
49             NestedMetaItem::Literal(ref lit) => {
50                 Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
51             }
52         }
53     }
54
55     /// Parses a `MetaItem` into a `Cfg`.
56     ///
57     /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or
58     /// `target_os = "redox"`.
59     ///
60     /// If the content is not properly formatted, it will return an error indicating what and where
61     /// the error is.
62     pub fn parse(cfg: &MetaItem) -> Result<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 => Ok(Cfg::Cfg(name, None)),
74             MetaItemKind::NameValue(ref lit) => match lit.kind {
75                 LitKind::Str(value, _) => Ok(Cfg::Cfg(name, Some(value))),
76                 _ => Err(InvalidCfgError {
77                     // FIXME: if the main #[cfg] syntax decided to support non-string literals,
78                     // this should be changed as well.
79                     msg: "value of cfg option should be a string literal",
80                     span: lit.span,
81                 }),
82             },
83             MetaItemKind::List(ref items) => {
84                 let mut sub_cfgs = items.iter().map(Cfg::parse_nested);
85                 match name {
86                     sym::all => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)),
87                     sym::any => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)),
88                     sym::not => {
89                         if sub_cfgs.len() == 1 {
90                             Ok(!sub_cfgs.next().unwrap()?)
91                         } else {
92                             Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span })
93                         }
94                     }
95                     _ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }),
96                 }
97             }
98         }
99     }
100
101     /// Checks whether the given configuration can be matched in the current session.
102     ///
103     /// Equivalent to `attr::cfg_matches`.
104     // FIXME: Actually make use of `features`.
105     pub fn matches(&self, parse_sess: &ParseSess, features: Option<&Features>) -> bool {
106         match *self {
107             Cfg::False => false,
108             Cfg::True => true,
109             Cfg::Not(ref child) => !child.matches(parse_sess, features),
110             Cfg::All(ref sub_cfgs) => {
111                 sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(parse_sess, features))
112             }
113             Cfg::Any(ref sub_cfgs) => {
114                 sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(parse_sess, features))
115             }
116             Cfg::Cfg(name, value) => parse_sess.config.contains(&(name, value)),
117         }
118     }
119
120     /// Whether the configuration consists of just `Cfg` or `Not`.
121     fn is_simple(&self) -> bool {
122         match *self {
123             Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
124             Cfg::All(..) | Cfg::Any(..) => false,
125         }
126     }
127
128     /// Whether the configuration consists of just `Cfg`, `Not` or `All`.
129     fn is_all(&self) -> bool {
130         match *self {
131             Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
132             Cfg::Any(..) => false,
133         }
134     }
135
136     /// Renders the configuration for human display, as a short HTML description.
137     pub(crate) fn render_short_html(&self) -> String {
138         let mut msg = Html(self, true).to_string();
139         if self.should_capitalize_first_letter() {
140             if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
141                 msg[i..i + 1].make_ascii_uppercase();
142             }
143         }
144         msg
145     }
146
147     /// Renders the configuration for long display, as a long HTML description.
148     pub(crate) fn render_long_html(&self) -> String {
149         let on = if self.should_use_with_in_description() { "with" } else { "on" };
150
151         let mut msg = format!("This is supported {} <strong>{}</strong>", on, Html(self, false));
152         if self.should_append_only_to_description() {
153             msg.push_str(" only");
154         }
155         msg.push('.');
156         msg
157     }
158
159     fn should_capitalize_first_letter(&self) -> bool {
160         match *self {
161             Cfg::False | Cfg::True | Cfg::Not(..) => true,
162             Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
163                 sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
164             }
165             Cfg::Cfg(name, _) => match &*name.as_str() {
166                 "debug_assertions" | "target_endian" => true,
167                 _ => false,
168             },
169         }
170     }
171
172     fn should_append_only_to_description(&self) -> bool {
173         match *self {
174             Cfg::False | Cfg::True => false,
175             Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
176             Cfg::Not(ref child) => match **child {
177                 Cfg::Cfg(..) => true,
178                 _ => false,
179             },
180         }
181     }
182
183     fn should_use_with_in_description(&self) -> bool {
184         match *self {
185             Cfg::Cfg(name, _) if name == sym::target_feature => true,
186             _ => false,
187         }
188     }
189 }
190
191 impl ops::Not for Cfg {
192     type Output = Cfg;
193     fn not(self) -> Cfg {
194         match self {
195             Cfg::False => Cfg::True,
196             Cfg::True => Cfg::False,
197             Cfg::Not(cfg) => *cfg,
198             s => Cfg::Not(Box::new(s)),
199         }
200     }
201 }
202
203 impl ops::BitAndAssign for Cfg {
204     fn bitand_assign(&mut self, other: Cfg) {
205         if *self == other {
206             return;
207         }
208         match (self, other) {
209             (&mut Cfg::False, _) | (_, Cfg::True) => {}
210             (s, Cfg::False) => *s = Cfg::False,
211             (s @ &mut Cfg::True, b) => *s = b,
212             (&mut Cfg::All(ref mut a), Cfg::All(ref mut b)) => a.append(b),
213             (&mut Cfg::All(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
214             (s, Cfg::All(mut a)) => {
215                 let b = mem::replace(s, Cfg::True);
216                 a.push(b);
217                 *s = Cfg::All(a);
218             }
219             (s, b) => {
220                 let a = mem::replace(s, Cfg::True);
221                 *s = Cfg::All(vec![a, b]);
222             }
223         }
224     }
225 }
226
227 impl ops::BitAnd for Cfg {
228     type Output = Cfg;
229     fn bitand(mut self, other: Cfg) -> Cfg {
230         self &= other;
231         self
232     }
233 }
234
235 impl ops::BitOrAssign for Cfg {
236     fn bitor_assign(&mut self, other: Cfg) {
237         if *self == other {
238             return;
239         }
240         match (self, other) {
241             (&mut Cfg::True, _) | (_, Cfg::False) => {}
242             (s, Cfg::True) => *s = Cfg::True,
243             (s @ &mut Cfg::False, b) => *s = b,
244             (&mut Cfg::Any(ref mut a), Cfg::Any(ref mut b)) => a.append(b),
245             (&mut Cfg::Any(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
246             (s, Cfg::Any(mut a)) => {
247                 let b = mem::replace(s, Cfg::True);
248                 a.push(b);
249                 *s = Cfg::Any(a);
250             }
251             (s, b) => {
252                 let a = mem::replace(s, Cfg::True);
253                 *s = Cfg::Any(vec![a, b]);
254             }
255         }
256     }
257 }
258
259 impl ops::BitOr for Cfg {
260     type Output = Cfg;
261     fn bitor(mut self, other: Cfg) -> Cfg {
262         self |= other;
263         self
264     }
265 }
266
267 /// Pretty-print wrapper for a `Cfg`. Also indicates whether the "short-form" rendering should be
268 /// used.
269 struct Html<'a>(&'a Cfg, bool);
270
271 fn write_with_opt_paren<T: fmt::Display>(
272     fmt: &mut fmt::Formatter<'_>,
273     has_paren: bool,
274     obj: T,
275 ) -> fmt::Result {
276     if has_paren {
277         fmt.write_char('(')?;
278     }
279     obj.fmt(fmt)?;
280     if has_paren {
281         fmt.write_char(')')?;
282     }
283     Ok(())
284 }
285
286 impl<'a> fmt::Display for Html<'a> {
287     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
288         match *self.0 {
289             Cfg::Not(ref child) => match **child {
290                 Cfg::Any(ref sub_cfgs) => {
291                     let separator =
292                         if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
293                     for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
294                         fmt.write_str(if i == 0 { "neither " } else { separator })?;
295                         write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
296                     }
297                     Ok(())
298                 }
299                 ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Html(simple, self.1)),
300                 ref c => write!(fmt, "not ({})", Html(c, self.1)),
301             },
302
303             Cfg::Any(ref sub_cfgs) => {
304                 let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };
305                 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
306                     if i != 0 {
307                         fmt.write_str(separator)?;
308                     }
309                     write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
310                 }
311                 Ok(())
312             }
313
314             Cfg::All(ref sub_cfgs) => {
315                 for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
316                     if i != 0 {
317                         fmt.write_str(" and ")?;
318                     }
319                     write_with_opt_paren(fmt, !sub_cfg.is_simple(), Html(sub_cfg, self.1))?;
320                 }
321                 Ok(())
322             }
323
324             Cfg::True => fmt.write_str("everywhere"),
325             Cfg::False => fmt.write_str("nowhere"),
326
327             Cfg::Cfg(name, value) => {
328                 let n = &*name.as_str();
329                 let human_readable = match (n, value) {
330                     ("unix", None) => "Unix",
331                     ("windows", None) => "Windows",
332                     ("debug_assertions", None) => "debug-assertions enabled",
333                     ("target_os", Some(os)) => match &*os.as_str() {
334                         "android" => "Android",
335                         "dragonfly" => "DragonFly BSD",
336                         "emscripten" => "Emscripten",
337                         "freebsd" => "FreeBSD",
338                         "fuchsia" => "Fuchsia",
339                         "haiku" => "Haiku",
340                         "hermit" => "HermitCore",
341                         "ios" => "iOS",
342                         "l4re" => "L4Re",
343                         "linux" => "Linux",
344                         "macos" => "macOS",
345                         "netbsd" => "NetBSD",
346                         "openbsd" => "OpenBSD",
347                         "redox" => "Redox",
348                         "solaris" => "Solaris",
349                         "windows" => "Windows",
350                         _ => "",
351                     },
352                     ("target_arch", Some(arch)) => match &*arch.as_str() {
353                         "aarch64" => "AArch64",
354                         "arm" => "ARM",
355                         "asmjs" => "JavaScript",
356                         "mips" => "MIPS",
357                         "mips64" => "MIPS-64",
358                         "msp430" => "MSP430",
359                         "powerpc" => "PowerPC",
360                         "powerpc64" => "PowerPC-64",
361                         "s390x" => "s390x",
362                         "sparc64" => "SPARC64",
363                         "wasm32" => "WebAssembly",
364                         "x86" => "x86",
365                         "x86_64" => "x86-64",
366                         _ => "",
367                     },
368                     ("target_vendor", Some(vendor)) => match &*vendor.as_str() {
369                         "apple" => "Apple",
370                         "pc" => "PC",
371                         "rumprun" => "Rumprun",
372                         "sun" => "Sun",
373                         "fortanix" => "Fortanix",
374                         _ => "",
375                     },
376                     ("target_env", Some(env)) => match &*env.as_str() {
377                         "gnu" => "GNU",
378                         "msvc" => "MSVC",
379                         "musl" => "musl",
380                         "newlib" => "Newlib",
381                         "uclibc" => "uClibc",
382                         "sgx" => "SGX",
383                         _ => "",
384                     },
385                     ("target_endian", Some(endian)) => return write!(fmt, "{}-endian", endian),
386                     ("target_pointer_width", Some(bits)) => return write!(fmt, "{}-bit", bits),
387                     ("target_feature", Some(feat)) => {
388                         if self.1 {
389                             return write!(fmt, "<code>{}</code>", feat);
390                         } else {
391                             return write!(fmt, "target feature <code>{}</code>", feat);
392                         }
393                     }
394                     _ => "",
395                 };
396                 if !human_readable.is_empty() {
397                     fmt.write_str(human_readable)
398                 } else if let Some(v) = value {
399                     write!(fmt, "<code>{}=\"{}\"</code>", Escape(n), Escape(&v.as_str()))
400                 } else {
401                     write!(fmt, "<code>{}</code>", Escape(n))
402                 }
403             }
404         }
405     }
406 }