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