1 //! The representation of a `#[doc(cfg(...))]` attribute.
3 // FIXME: Once the portability lint RFC is implemented (see tracking issue #41619),
4 // switch to use those structures instead.
6 use std
::fmt
::{self, Write}
;
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}
;
18 use crate::html
::escape
::Escape
;
23 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
25 /// Accepts all configurations.
27 /// Denies all configurations.
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)`.
33 /// Union of a list of configuration requirements, i.e., `any(...)`.
35 /// Intersection of a list of configuration requirements, i.e., `all(...)`.
39 #[derive(PartialEq, Debug)]
40 pub(crate) struct InvalidCfgError
{
41 pub(crate) msg
: &'
static str,
42 pub(crate) span
: Span
,
46 /// Parses a `NestedMetaItem` into a `Cfg`.
48 nested_cfg
: &NestedMetaItem
,
49 exclude
: &FxHashSet
<Cfg
>,
50 ) -> Result
<Option
<Cfg
>, InvalidCfgError
> {
52 NestedMetaItem
::MetaItem(ref cfg
) => Cfg
::parse_without(cfg
, exclude
),
53 NestedMetaItem
::Lit(ref lit
) => {
54 Err(InvalidCfgError { msg: "unexpected literal", span: lit.span }
)
59 pub(crate) fn parse_without(
61 exclude
: &FxHashSet
<Cfg
>,
62 ) -> Result
<Option
<Cfg
>, InvalidCfgError
> {
63 let name
= match cfg
.ident() {
64 Some(ident
) => ident
.name
,
66 return Err(InvalidCfgError
{
67 msg
: "expected a single identifier",
73 MetaItemKind
::Word
=> {
74 let cfg
= Cfg
::Cfg(name
, None
);
75 if exclude
.contains(&cfg
) { Ok(None) }
else { Ok(Some(cfg)) }
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)) }
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",
89 MetaItemKind
::List(ref items
) => {
90 let orig_len
= items
.len();
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?
)),
98 let mut sub_cfgs
= sub_cfgs
.collect
::<Vec
<_
>>();
99 if sub_cfgs
.len() == 1 {
100 Ok(!sub_cfgs
.pop().unwrap()?
)
105 Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span }
)
108 _
=> Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }
),
111 Ok(c
) => Ok(Some(c
)),
118 /// Parses a `MetaItem` into a `Cfg`.
120 /// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or
121 /// `target_os = "redox"`.
123 /// If the content is not properly formatted, it will return an error indicating what and where
125 pub(crate) fn parse(cfg
: &MetaItem
) -> Result
<Cfg
, InvalidCfgError
> {
126 Self::parse_without(cfg
, &FxHashSet
::default()).map(|ret
| ret
.unwrap())
129 /// Checks whether the given configuration can be matched in the current session.
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
{
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
))
141 Cfg
::Any(ref sub_cfgs
) => {
142 sub_cfgs
.iter().any(|sub_cfg
| sub_cfg
.matches(parse_sess
, features
))
144 Cfg
::Cfg(name
, value
) => parse_sess
.config
.contains(&(name
, value
)),
148 /// Whether the configuration consists of just `Cfg` or `Not`.
149 fn is_simple(&self) -> bool
{
151 Cfg
::False
| Cfg
::True
| Cfg
::Cfg(..) | Cfg
::Not(..) => true,
152 Cfg
::All(..) | Cfg
::Any(..) => false,
156 /// Whether the configuration consists of just `Cfg`, `Not` or `All`.
157 fn is_all(&self) -> bool
{
159 Cfg
::False
| Cfg
::True
| Cfg
::Cfg(..) | Cfg
::Not(..) | Cfg
::All(..) => true,
160 Cfg
::Any(..) => false,
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();
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" }
;
180 format
!("Available {on} <strong>{}</strong>", Display(self, Format
::LongHtml
));
181 if self.should_append_only_to_description() {
182 msg
.push_str(" only");
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" }
;
192 let mut msg
= format
!("Available {on} {}", Display(self, Format
::LongPlain
));
193 if self.should_append_only_to_description() {
194 msg
.push_str(" only");
199 fn should_capitalize_first_letter(&self) -> bool
{
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)
205 Cfg
::Cfg(name
, _
) => name
== sym
::debug_assertions
|| name
== sym
::target_endian
,
209 fn should_append_only_to_description(&self) -> bool
{
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,
218 fn should_use_with_in_description(&self) -> bool
{
219 matches
!(self, Cfg
::Cfg(sym
::target_feature
, _
))
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
226 /// See `tests::test_simplify_with` for examples.
227 pub(crate) fn simplify_with(&self, assume
: &Cfg
) -> Option
<Cfg
> {
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()
236 a
.iter().filter(|&a
| a
!= assume
).cloned().collect()
238 let len
= sub_cfgs
.len();
242 _
=> Some(Cfg
::All(sub_cfgs
)),
244 } else if let Cfg
::All(b
) = assume
{
245 if b
.contains(self) {
254 impl ops
::Not
for Cfg
{
256 fn not(self) -> Cfg
{
258 Cfg
::False
=> Cfg
::True
,
259 Cfg
::True
=> Cfg
::False
,
260 Cfg
::Not(cfg
) => *cfg
,
261 s
=> Cfg
::Not(Box
::new(s
)),
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(..) {
279 (&mut Cfg
::All(ref mut a
), ref mut b
) => {
281 a
.push(mem
::replace(b
, Cfg
::True
));
284 (s
, Cfg
::All(mut a
)) => {
285 let b
= mem
::replace(s
, Cfg
::True
);
293 let a
= mem
::replace(s
, Cfg
::True
);
294 *s
= Cfg
::All(vec
![a
, b
]);
301 impl ops
::BitAnd
for Cfg
{
303 fn bitand(mut self, other
: Cfg
) -> Cfg
{
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(..) {
321 (&mut Cfg
::Any(ref mut a
), ref mut b
) => {
323 a
.push(mem
::replace(b
, Cfg
::True
));
326 (s
, Cfg
::Any(mut a
)) => {
327 let b
= mem
::replace(s
, Cfg
::True
);
335 let a
= mem
::replace(s
, Cfg
::True
);
336 *s
= Cfg
::Any(vec
![a
, b
]);
343 impl ops
::BitOr
for Cfg
{
345 fn bitor(mut self, other
: Cfg
) -> Cfg
{
351 #[derive(Clone, Copy)]
359 fn is_long(self) -> bool
{
361 Format
::LongHtml
| Format
::LongPlain
=> true,
362 Format
::ShortHtml
=> false,
366 fn is_html(self) -> bool
{
368 Format
::LongHtml
| Format
::ShortHtml
=> true,
369 Format
::LongPlain
=> false,
374 /// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
375 struct Display
<'a
>(&'a Cfg
, Format
);
377 fn write_with_opt_paren
<T
: fmt
::Display
>(
378 fmt
: &mut fmt
::Formatter
<'_
>,
383 fmt
.write_char('
('
)?
;
387 fmt
.write_char('
)'
)?
;
392 impl<'a
> fmt
::Display
for Display
<'a
> {
393 fn fmt(&self, fmt
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
395 Cfg
::Not(ref child
) => match **child
{
396 Cfg
::Any(ref sub_cfgs
) => {
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))?
;
405 ref simple @ Cfg
::Cfg(..) => write
!(fmt
, "non-{}", Display(simple
, self.1)),
406 ref c
=> write
!(fmt
, "not ({})", Display(c
, self.1)),
409 Cfg
::Any(ref sub_cfgs
) => {
410 let separator
= if sub_cfgs
.iter().all(Cfg
::is_simple
) { " or " }
else { ", or " }
;
412 let short_longhand
= self.1.is_long
() && {
413 let all_crate_features
= sub_cfgs
415 .all(|sub_cfg
| matches
!(sub_cfg
, Cfg
::Cfg(sym
::feature
, Some(_
))));
416 let all_target_features
= sub_cfgs
418 .all(|sub_cfg
| matches
!(sub_cfg
, Cfg
::Cfg(sym
::target_feature
, Some(_
))));
420 if all_crate_features
{
421 fmt
.write_str("crate features ")?
;
423 } else if all_target_features
{
424 fmt
.write_str("target features ")?
;
431 for (i
, sub_cfg
) in sub_cfgs
.iter().enumerate() {
433 fmt
.write_str(separator
)?
;
435 if let (true, Cfg
::Cfg(_
, Some(feat
))) = (short_longhand
, sub_cfg
) {
436 if self.1.is_html
() {
437 write
!(fmt
, "<code>{}</code>", feat
)?
;
439 write
!(fmt
, "`{}`", feat
)?
;
442 write_with_opt_paren(fmt
, !sub_cfg
.is_all(), Display(sub_cfg
, self.1))?
;
448 Cfg
::All(ref sub_cfgs
) => {
449 let short_longhand
= self.1.is_long
() && {
450 let all_crate_features
= sub_cfgs
452 .all(|sub_cfg
| matches
!(sub_cfg
, Cfg
::Cfg(sym
::feature
, Some(_
))));
453 let all_target_features
= sub_cfgs
455 .all(|sub_cfg
| matches
!(sub_cfg
, Cfg
::Cfg(sym
::target_feature
, Some(_
))));
457 if all_crate_features
{
458 fmt
.write_str("crate features ")?
;
460 } else if all_target_features
{
461 fmt
.write_str("target features ")?
;
468 for (i
, sub_cfg
) in sub_cfgs
.iter().enumerate() {
470 fmt
.write_str(" and ")?
;
472 if let (true, Cfg
::Cfg(_
, Some(feat
))) = (short_longhand
, sub_cfg
) {
473 if self.1.is_html
() {
474 write
!(fmt
, "<code>{}</code>", feat
)?
;
476 write
!(fmt
, "`{}`", feat
)?
;
479 write_with_opt_paren(fmt
, !sub_cfg
.is_simple(), Display(sub_cfg
, self.1))?
;
485 Cfg
::True
=> fmt
.write_str("everywhere"),
486 Cfg
::False
=> fmt
.write_str("nowhere"),
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",
500 "hermit" => "HermitCore",
501 "illumos" => "illumos",
506 "netbsd" => "NetBSD",
507 "openbsd" => "OpenBSD",
509 "solaris" => "Solaris",
511 "windows" => "Windows",
514 (sym
::target_arch
, Some(arch
)) => match arch
.as_str() {
515 "aarch64" => "AArch64",
517 "asmjs" => "JavaScript",
520 "mips64" => "MIPS-64",
521 "msp430" => "MSP430",
522 "powerpc" => "PowerPC",
523 "powerpc64" => "PowerPC-64",
524 "riscv32" => "RISC-V RV32",
525 "riscv64" => "RISC-V RV64",
527 "sparc64" => "SPARC64",
528 "wasm32" | "wasm64" => "WebAssembly",
530 "x86_64" => "x86-64",
533 (sym
::target_vendor
, Some(vendor
)) => match vendor
.as_str() {
537 "fortanix" => "Fortanix",
540 (sym
::target_env
, Some(env
)) => match env
.as_str() {
544 "newlib" => "Newlib",
545 "uclibc" => "uClibc",
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
);
555 Format
::LongPlain
=> return write
!(fmt
, "target feature `{}`", feat
),
556 Format
::ShortHtml
=> return write
!(fmt
, "<code>{}</code>", feat
),
558 (sym
::feature
, Some(feat
)) => match self.1 {
559 Format
::LongHtml
=> {
560 return write
!(fmt
, "crate feature <code>{}</code>", feat
);
562 Format
::LongPlain
=> return write
!(fmt
, "crate feature `{}`", feat
),
563 Format
::ShortHtml
=> return write
!(fmt
, "<code>{}</code>", feat
),
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
() {
573 r
#"<code>{}="{}"</code>"#,
574 Escape(name
.as_str()),
578 write
!(fmt
, r
#"`{}="{}"`"#, name, v)
580 } else if self.1.is_html
() {
581 write
!(fmt
, "<code>{}</code>", Escape(name
.as_str()))
583 write
!(fmt
, "`{}`", name
)