blob: 078948cc63bd2633822deaf6dee0814dd8d0546e [file] [log] [blame]
//! The representation of a `#[doc(cfg(...))]` attribute.
// FIXME: Once the portability lint RFC is implemented (see tracking issue #41619),
// switch to use those structures instead.
use std::mem;
use std::fmt::{self, Write};
use std::ops;
use rustc_feature::Features;
use syntax::symbol::{Symbol, sym};
use syntax::ast::{MetaItem, MetaItemKind, NestedMetaItem, LitKind};
use syntax::sess::ParseSess;
use syntax_pos::Span;
use crate::html::escape::Escape;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Cfg {
/// Accepts all configurations.
True,
/// Denies all configurations.
False,
/// A generic configuration option, e.g., `test` or `target_os = "linux"`.
Cfg(Symbol, Option<Symbol>),
/// Negates a configuration requirement, i.e., `not(x)`.
Not(Box<Cfg>),
/// Union of a list of configuration requirements, i.e., `any(...)`.
Any(Vec<Cfg>),
/// Intersection of a list of configuration requirements, i.e., `all(...)`.
All(Vec<Cfg>),
}
#[derive(PartialEq, Debug)]
pub struct InvalidCfgError {
pub msg: &'static str,
pub span: Span,
}
impl Cfg {
/// Parses a `NestedMetaItem` into a `Cfg`.
fn parse_nested(nested_cfg: &NestedMetaItem) -> Result<Cfg, InvalidCfgError> {
match nested_cfg {
NestedMetaItem::MetaItem(ref cfg) => Cfg::parse(cfg),
NestedMetaItem::Literal(ref lit) => Err(InvalidCfgError {
msg: "unexpected literal",
span: lit.span,
}),
}
}
/// Parses a `MetaItem` into a `Cfg`.
///
/// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or
/// `target_os = "redox"`.
///
/// If the content is not properly formatted, it will return an error indicating what and where
/// the error is.
pub fn parse(cfg: &MetaItem) -> Result<Cfg, InvalidCfgError> {
let name = match cfg.ident() {
Some(ident) => ident.name,
None => return Err(InvalidCfgError {
msg: "expected a single identifier",
span: cfg.span
}),
};
match cfg.kind {
MetaItemKind::Word => Ok(Cfg::Cfg(name, None)),
MetaItemKind::NameValue(ref lit) => match lit.kind {
LitKind::Str(value, _) => Ok(Cfg::Cfg(name, Some(value))),
_ => Err(InvalidCfgError {
// FIXME: if the main #[cfg] syntax decided to support non-string literals,
// this should be changed as well.
msg: "value of cfg option should be a string literal",
span: lit.span,
}),
},
MetaItemKind::List(ref items) => {
let mut sub_cfgs = items.iter().map(Cfg::parse_nested);
match name {
sym::all => sub_cfgs.fold(Ok(Cfg::True), |x, y| Ok(x? & y?)),
sym::any => sub_cfgs.fold(Ok(Cfg::False), |x, y| Ok(x? | y?)),
sym::not => if sub_cfgs.len() == 1 {
Ok(!sub_cfgs.next().unwrap()?)
} else {
Err(InvalidCfgError {
msg: "expected 1 cfg-pattern",
span: cfg.span,
})
},
_ => Err(InvalidCfgError {
msg: "invalid predicate",
span: cfg.span,
}),
}
}
}
}
/// Checks whether the given configuration can be matched in the current session.
///
/// Equivalent to `attr::cfg_matches`.
// FIXME: Actually make use of `features`.
pub fn matches(&self, parse_sess: &ParseSess, features: Option<&Features>) -> bool {
match *self {
Cfg::False => false,
Cfg::True => true,
Cfg::Not(ref child) => !child.matches(parse_sess, features),
Cfg::All(ref sub_cfgs) => {
sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(parse_sess, features))
},
Cfg::Any(ref sub_cfgs) => {
sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(parse_sess, features))
},
Cfg::Cfg(name, value) => parse_sess.config.contains(&(name, value)),
}
}
/// Whether the configuration consists of just `Cfg` or `Not`.
fn is_simple(&self) -> bool {
match *self {
Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
Cfg::All(..) | Cfg::Any(..) => false,
}
}
/// Whether the configuration consists of just `Cfg`, `Not` or `All`.
fn is_all(&self) -> bool {
match *self {
Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
Cfg::Any(..) => false,
}
}
/// Renders the configuration for human display, as a short HTML description.
pub(crate) fn render_short_html(&self) -> String {
let mut msg = Html(self, true).to_string();
if self.should_capitalize_first_letter() {
if let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) {
msg[i .. i+1].make_ascii_uppercase();
}
}
msg
}
/// Renders the configuration for long display, as a long HTML description.
pub(crate) fn render_long_html(&self) -> String {
let on = if self.should_use_with_in_description() {
"with"
} else {
"on"
};
let mut msg = format!("This is supported {} <strong>{}</strong>", on, Html(self, false));
if self.should_append_only_to_description() {
msg.push_str(" only");
}
msg.push('.');
msg
}
fn should_capitalize_first_letter(&self) -> bool {
match *self {
Cfg::False | Cfg::True | Cfg::Not(..) => true,
Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
},
Cfg::Cfg(name, _) => match &*name.as_str() {
"debug_assertions" | "target_endian" => true,
_ => false,
},
}
}
fn should_append_only_to_description(&self) -> bool {
match *self {
Cfg::False | Cfg::True => false,
Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
Cfg::Not(ref child) => match **child {
Cfg::Cfg(..) => true,
_ => false,
}
}
}
fn should_use_with_in_description(&self) -> bool {
match *self {
Cfg::Cfg(name, _) if name == sym::target_feature => true,
_ => false,
}
}
}
impl ops::Not for Cfg {
type Output = Cfg;
fn not(self) -> Cfg {
match self {
Cfg::False => Cfg::True,
Cfg::True => Cfg::False,
Cfg::Not(cfg) => *cfg,
s => Cfg::Not(Box::new(s)),
}
}
}
impl ops::BitAndAssign for Cfg {
fn bitand_assign(&mut self, other: Cfg) {
match (self, other) {
(&mut Cfg::False, _) | (_, Cfg::True) => {},
(s, Cfg::False) => *s = Cfg::False,
(s @ &mut Cfg::True, b) => *s = b,
(&mut Cfg::All(ref mut a), Cfg::All(ref mut b)) => a.append(b),
(&mut Cfg::All(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
(s, Cfg::All(mut a)) => {
let b = mem::replace(s, Cfg::True);
a.push(b);
*s = Cfg::All(a);
},
(s, b) => {
let a = mem::replace(s, Cfg::True);
*s = Cfg::All(vec![a, b]);
},
}
}
}
impl ops::BitAnd for Cfg {
type Output = Cfg;
fn bitand(mut self, other: Cfg) -> Cfg {
self &= other;
self
}
}
impl ops::BitOrAssign for Cfg {
fn bitor_assign(&mut self, other: Cfg) {
match (self, other) {
(&mut Cfg::True, _) | (_, Cfg::False) => {},
(s, Cfg::True) => *s = Cfg::True,
(s @ &mut Cfg::False, b) => *s = b,
(&mut Cfg::Any(ref mut a), Cfg::Any(ref mut b)) => a.append(b),
(&mut Cfg::Any(ref mut a), ref mut b) => a.push(mem::replace(b, Cfg::True)),
(s, Cfg::Any(mut a)) => {
let b = mem::replace(s, Cfg::True);
a.push(b);
*s = Cfg::Any(a);
},
(s, b) => {
let a = mem::replace(s, Cfg::True);
*s = Cfg::Any(vec![a, b]);
},
}
}
}
impl ops::BitOr for Cfg {
type Output = Cfg;
fn bitor(mut self, other: Cfg) -> Cfg {
self |= other;
self
}
}
/// Pretty-print wrapper for a `Cfg`. Also indicates whether the "short-form" rendering should be
/// used.
struct Html<'a>(&'a Cfg, bool);
fn write_with_opt_paren<T: fmt::Display>(
fmt: &mut fmt::Formatter<'_>,
has_paren: bool,
obj: T,
) -> fmt::Result {
if has_paren {
fmt.write_char('(')?;
}
obj.fmt(fmt)?;
if has_paren {
fmt.write_char(')')?;
}
Ok(())
}
impl<'a> fmt::Display for Html<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self.0 {
Cfg::Not(ref child) => match **child {
Cfg::Any(ref sub_cfgs) => {
let separator = if sub_cfgs.iter().all(Cfg::is_simple) {
" nor "
} else {
", nor "
};
for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
fmt.write_str(if i == 0 { "neither " } else { separator })?;
write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
}
Ok(())
}
ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Html(simple, self.1)),
ref c => write!(fmt, "not ({})", Html(c, self.1)),
},
Cfg::Any(ref sub_cfgs) => {
let separator = if sub_cfgs.iter().all(Cfg::is_simple) {
" or "
} else {
", or "
};
for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
if i != 0 {
fmt.write_str(separator)?;
}
write_with_opt_paren(fmt, !sub_cfg.is_all(), Html(sub_cfg, self.1))?;
}
Ok(())
},
Cfg::All(ref sub_cfgs) => {
for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
if i != 0 {
fmt.write_str(" and ")?;
}
write_with_opt_paren(fmt, !sub_cfg.is_simple(), Html(sub_cfg, self.1))?;
}
Ok(())
},
Cfg::True => fmt.write_str("everywhere"),
Cfg::False => fmt.write_str("nowhere"),
Cfg::Cfg(name, value) => {
let n = &*name.as_str();
let human_readable = match (n, value) {
("unix", None) => "Unix",
("windows", None) => "Windows",
("debug_assertions", None) => "debug-assertions enabled",
("target_os", Some(os)) => match &*os.as_str() {
"android" => "Android",
"dragonfly" => "DragonFly BSD",
"emscripten" => "Emscripten",
"freebsd" => "FreeBSD",
"fuchsia" => "Fuchsia",
"haiku" => "Haiku",
"hermit" => "HermitCore",
"ios" => "iOS",
"l4re" => "L4Re",
"linux" => "Linux",
"macos" => "macOS",
"netbsd" => "NetBSD",
"openbsd" => "OpenBSD",
"redox" => "Redox",
"solaris" => "Solaris",
"windows" => "Windows",
_ => "",
},
("target_arch", Some(arch)) => match &*arch.as_str() {
"aarch64" => "AArch64",
"arm" => "ARM",
"asmjs" => "JavaScript",
"mips" => "MIPS",
"mips64" => "MIPS-64",
"msp430" => "MSP430",
"powerpc" => "PowerPC",
"powerpc64" => "PowerPC-64",
"s390x" => "s390x",
"sparc64" => "SPARC64",
"wasm32" => "WebAssembly",
"x86" => "x86",
"x86_64" => "x86-64",
_ => "",
},
("target_vendor", Some(vendor)) => match &*vendor.as_str() {
"apple" => "Apple",
"pc" => "PC",
"rumprun" => "Rumprun",
"sun" => "Sun",
"fortanix" => "Fortanix",
_ => ""
},
("target_env", Some(env)) => match &*env.as_str() {
"gnu" => "GNU",
"msvc" => "MSVC",
"musl" => "musl",
"newlib" => "Newlib",
"uclibc" => "uClibc",
"sgx" => "SGX",
_ => "",
},
("target_endian", Some(endian)) => return write!(fmt, "{}-endian", endian),
("target_pointer_width", Some(bits)) => return write!(fmt, "{}-bit", bits),
("target_feature", Some(feat)) =>
if self.1 {
return write!(fmt, "<code>{}</code>", feat);
} else {
return write!(fmt, "target feature <code>{}</code>", feat);
},
_ => "",
};
if !human_readable.is_empty() {
fmt.write_str(human_readable)
} else if let Some(v) = value {
write!(fmt, "<code>{}=\"{}\"</code>", Escape(n), Escape(&v.as_str()))
} else {
write!(fmt, "<code>{}</code>", Escape(n))
}
}
}
}
}