mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-26 13:01:27 +03:00
857 lines
29 KiB
Rust
857 lines
29 KiB
Rust
//! 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::str::FromStr;
|
|
use std::sync::Arc;
|
|
use std::{fmt, mem, ops};
|
|
|
|
use itertools::Either;
|
|
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
|
use rustc_data_structures::thin_vec::{ThinVec, thin_vec};
|
|
use rustc_hir as hir;
|
|
use rustc_hir::Attribute;
|
|
use rustc_hir::attrs::{self, AttributeKind, CfgEntry, CfgHideShow, HideOrShow};
|
|
use rustc_middle::ty::TyCtxt;
|
|
use rustc_span::symbol::{Symbol, sym};
|
|
use rustc_span::{DUMMY_SP, Span};
|
|
use rustc_target::spec;
|
|
|
|
use crate::display::{Joined as _, MaybeDisplay, Wrapped};
|
|
use crate::html::escape::Escape;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
#[derive(Clone, Debug, Hash)]
|
|
// Because `CfgEntry` includes `Span`, we must NEVER use `==`/`!=` operators on `Cfg` and instead
|
|
// use `is_equivalent_to`.
|
|
#[cfg_attr(test, derive(PartialEq))]
|
|
pub(crate) struct Cfg(CfgEntry);
|
|
|
|
/// Whether the configuration consists of just `Cfg` or `Not`.
|
|
fn is_simple_cfg(cfg: &CfgEntry) -> bool {
|
|
match cfg {
|
|
CfgEntry::Bool(..)
|
|
| CfgEntry::NameValue { .. }
|
|
| CfgEntry::Not(..)
|
|
| CfgEntry::Version(..) => true,
|
|
CfgEntry::All(..) | CfgEntry::Any(..) => false,
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if is [`CfgEntry::Any`], otherwise returns `false`.
|
|
fn is_any_cfg(cfg: &CfgEntry) -> bool {
|
|
match cfg {
|
|
CfgEntry::Bool(..)
|
|
| CfgEntry::NameValue { .. }
|
|
| CfgEntry::Not(..)
|
|
| CfgEntry::Version(..)
|
|
| CfgEntry::All(..) => false,
|
|
CfgEntry::Any(..) => true,
|
|
}
|
|
}
|
|
|
|
fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashSet<NameValueCfg>) -> Option<CfgEntry> {
|
|
match cfg {
|
|
CfgEntry::Bool(..) => Some(cfg.clone()),
|
|
CfgEntry::NameValue { .. } => {
|
|
if !hidden.contains(&NameValueCfg::from(cfg)) {
|
|
Some(cfg.clone())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
CfgEntry::Not(cfg, _) => {
|
|
if let Some(cfg) = strip_hidden(cfg, hidden) {
|
|
Some(CfgEntry::Not(Box::new(cfg), DUMMY_SP))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
CfgEntry::Any(cfgs, _) => {
|
|
let cfgs =
|
|
cfgs.iter().filter_map(|cfg| strip_hidden(cfg, hidden)).collect::<ThinVec<_>>();
|
|
if cfgs.is_empty() { None } else { Some(CfgEntry::Any(cfgs, DUMMY_SP)) }
|
|
}
|
|
CfgEntry::All(cfgs, _) => {
|
|
let cfgs =
|
|
cfgs.iter().filter_map(|cfg| strip_hidden(cfg, hidden)).collect::<ThinVec<_>>();
|
|
if cfgs.is_empty() { None } else { Some(CfgEntry::All(cfgs, DUMMY_SP)) }
|
|
}
|
|
CfgEntry::Version(..) => {
|
|
// FIXME: Should be handled.
|
|
Some(cfg.clone())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn should_capitalize_first_letter(cfg: &CfgEntry) -> bool {
|
|
match cfg {
|
|
CfgEntry::Bool(..) | CfgEntry::Not(..) | CfgEntry::Version(..) => true,
|
|
CfgEntry::Any(sub_cfgs, _) | CfgEntry::All(sub_cfgs, _) => {
|
|
sub_cfgs.first().map(should_capitalize_first_letter).unwrap_or(false)
|
|
}
|
|
CfgEntry::NameValue { name, .. } => {
|
|
*name == sym::debug_assertions || *name == sym::target_endian
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Cfg {
|
|
/// Renders the configuration for human display, as a short HTML description.
|
|
pub(crate) fn render_short_html(&self) -> String {
|
|
let mut msg = Display(&self.0, Format::ShortHtml).to_string();
|
|
if should_capitalize_first_letter(&self.0)
|
|
&& let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric())
|
|
{
|
|
msg[i..i + 1].make_ascii_uppercase();
|
|
}
|
|
msg
|
|
}
|
|
|
|
fn render_long_inner(&self, format: Format) -> String {
|
|
let on = if self.omit_preposition() {
|
|
" "
|
|
} else if self.should_use_with_in_description() {
|
|
" with "
|
|
} else {
|
|
" on "
|
|
};
|
|
|
|
let mut msg = if matches!(format, Format::LongHtml) {
|
|
format!("Available{on}<strong>{}</strong>", Display(&self.0, format))
|
|
} else {
|
|
format!("Available{on}{}", Display(&self.0, format))
|
|
};
|
|
if self.should_append_only_to_description() {
|
|
msg.push_str(" only");
|
|
}
|
|
msg
|
|
}
|
|
|
|
/// Renders the configuration for long display, as a long HTML description.
|
|
pub(crate) fn render_long_html(&self) -> String {
|
|
let mut msg = self.render_long_inner(Format::LongHtml);
|
|
msg.push('.');
|
|
msg
|
|
}
|
|
|
|
/// Renders the configuration for long display, as a long plain text description.
|
|
pub(crate) fn render_long_plain(&self) -> String {
|
|
self.render_long_inner(Format::LongPlain)
|
|
}
|
|
|
|
fn should_append_only_to_description(&self) -> bool {
|
|
match self.0 {
|
|
CfgEntry::Any(..)
|
|
| CfgEntry::All(..)
|
|
| CfgEntry::NameValue { .. }
|
|
| CfgEntry::Version(..)
|
|
| CfgEntry::Not(box CfgEntry::NameValue { .. }, _) => true,
|
|
CfgEntry::Not(..) | CfgEntry::Bool(..) => false,
|
|
}
|
|
}
|
|
|
|
fn should_use_with_in_description(&self) -> bool {
|
|
matches!(self.0, CfgEntry::NameValue { name, .. } if name == sym::target_feature)
|
|
}
|
|
|
|
/// Attempt to simplify this cfg by assuming that `assume` is already known to be true, will
|
|
/// return `None` if simplification managed to completely eliminate any requirements from this
|
|
/// `Cfg`.
|
|
///
|
|
/// See `tests::test_simplify_with` for examples.
|
|
pub(crate) fn simplify_with(&self, assume: &Self) -> Option<Self> {
|
|
if self.0.is_equivalent_to(&assume.0) {
|
|
None
|
|
} else if let CfgEntry::All(a, _) = &self.0 {
|
|
let mut sub_cfgs: ThinVec<CfgEntry> = if let CfgEntry::All(b, _) = &assume.0 {
|
|
a.iter().filter(|a| !b.iter().any(|b| a.is_equivalent_to(b))).cloned().collect()
|
|
} else {
|
|
a.iter().filter(|&a| !a.is_equivalent_to(&assume.0)).cloned().collect()
|
|
};
|
|
let len = sub_cfgs.len();
|
|
match len {
|
|
0 => None,
|
|
1 => sub_cfgs.pop().map(Cfg),
|
|
_ => Some(Cfg(CfgEntry::All(sub_cfgs, DUMMY_SP))),
|
|
}
|
|
} else if let CfgEntry::All(b, _) = &assume.0
|
|
&& b.iter().any(|b| b.is_equivalent_to(&self.0))
|
|
{
|
|
None
|
|
} else {
|
|
Some(self.clone())
|
|
}
|
|
}
|
|
|
|
fn omit_preposition(&self) -> bool {
|
|
matches!(self.0, CfgEntry::Bool(..))
|
|
}
|
|
|
|
pub(crate) fn inner(&self) -> &CfgEntry {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl ops::Not for Cfg {
|
|
type Output = Cfg;
|
|
fn not(self) -> Cfg {
|
|
Cfg(match self.0 {
|
|
CfgEntry::Bool(v, s) => CfgEntry::Bool(!v, s),
|
|
CfgEntry::Not(cfg, _) => *cfg,
|
|
s => CfgEntry::Not(Box::new(s), DUMMY_SP),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ops::BitAndAssign for Cfg {
|
|
fn bitand_assign(&mut self, other: Cfg) {
|
|
match (&mut self.0, other.0) {
|
|
(CfgEntry::Bool(false, _), _) | (_, CfgEntry::Bool(true, _)) => {}
|
|
(s, CfgEntry::Bool(false, _)) => *s = CfgEntry::Bool(false, DUMMY_SP),
|
|
(s @ CfgEntry::Bool(true, _), b) => *s = b,
|
|
(CfgEntry::All(a, _), CfgEntry::All(ref mut b, _)) => {
|
|
for c in b.drain(..) {
|
|
if !a.iter().any(|a| a.is_equivalent_to(&c)) {
|
|
a.push(c);
|
|
}
|
|
}
|
|
}
|
|
(CfgEntry::All(a, _), ref mut b) => {
|
|
if !a.iter().any(|a| a.is_equivalent_to(b)) {
|
|
a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP)));
|
|
}
|
|
}
|
|
(s, CfgEntry::All(mut a, _)) => {
|
|
let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
|
|
if !a.iter().any(|a| a.is_equivalent_to(&b)) {
|
|
a.push(b);
|
|
}
|
|
*s = CfgEntry::All(a, DUMMY_SP);
|
|
}
|
|
(s, b) => {
|
|
if !s.is_equivalent_to(&b) {
|
|
let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
|
|
*s = CfgEntry::All(thin_vec![a, b], DUMMY_SP);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (&mut self.0, other.0) {
|
|
(CfgEntry::Bool(true, _), _)
|
|
| (_, CfgEntry::Bool(false, _))
|
|
| (_, CfgEntry::Bool(true, _)) => {}
|
|
(s @ CfgEntry::Bool(false, _), b) => *s = b,
|
|
(CfgEntry::Any(a, _), CfgEntry::Any(ref mut b, _)) => {
|
|
for c in b.drain(..) {
|
|
if !a.iter().any(|a| a.is_equivalent_to(&c)) {
|
|
a.push(c);
|
|
}
|
|
}
|
|
}
|
|
(CfgEntry::Any(a, _), ref mut b) => {
|
|
if !a.iter().any(|a| a.is_equivalent_to(b)) {
|
|
a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP)));
|
|
}
|
|
}
|
|
(s, CfgEntry::Any(mut a, _)) => {
|
|
let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
|
|
if !a.iter().any(|a| a.is_equivalent_to(&b)) {
|
|
a.push(b);
|
|
}
|
|
*s = CfgEntry::Any(a, DUMMY_SP);
|
|
}
|
|
(s, b) => {
|
|
if !s.is_equivalent_to(&b) {
|
|
let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
|
|
*s = CfgEntry::Any(thin_vec![a, b], DUMMY_SP);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ops::BitOr for Cfg {
|
|
type Output = Cfg;
|
|
fn bitor(mut self, other: Cfg) -> Cfg {
|
|
self |= other;
|
|
self
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum Format {
|
|
LongHtml,
|
|
LongPlain,
|
|
ShortHtml,
|
|
}
|
|
|
|
impl Format {
|
|
fn is_long(self) -> bool {
|
|
match self {
|
|
Format::LongHtml | Format::LongPlain => true,
|
|
Format::ShortHtml => false,
|
|
}
|
|
}
|
|
|
|
fn is_html(self) -> bool {
|
|
match self {
|
|
Format::LongHtml | Format::ShortHtml => true,
|
|
Format::LongPlain => false,
|
|
}
|
|
}
|
|
|
|
fn escape(self, s: &str) -> impl fmt::Display {
|
|
if self.is_html() { Either::Left(Escape(s)) } else { Either::Right(s) }
|
|
}
|
|
}
|
|
|
|
/// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
|
|
struct Display<'a>(&'a CfgEntry, Format);
|
|
|
|
impl Display<'_> {
|
|
fn code_wrappers(&self) -> Wrapped<&'static str> {
|
|
if self.1.is_html() { Wrapped::with("<code>", "</code>") } else { Wrapped::with("`", "`") }
|
|
}
|
|
|
|
fn display_sub_cfgs(
|
|
&self,
|
|
fmt: &mut fmt::Formatter<'_>,
|
|
sub_cfgs: &[CfgEntry],
|
|
separator: &str,
|
|
) -> fmt::Result {
|
|
use fmt::Display as _;
|
|
|
|
let short_longhand = self.1.is_long() && {
|
|
let all_crate_features = sub_cfgs.iter().all(|sub_cfg| {
|
|
matches!(sub_cfg, CfgEntry::NameValue { name: sym::feature, value: Some(_), .. })
|
|
});
|
|
let all_target_features = sub_cfgs.iter().all(|sub_cfg| {
|
|
matches!(
|
|
sub_cfg,
|
|
CfgEntry::NameValue { name: sym::target_feature, value: Some(_), .. }
|
|
)
|
|
});
|
|
|
|
if all_crate_features {
|
|
fmt.write_str("crate features ")?;
|
|
true
|
|
} else if all_target_features {
|
|
fmt.write_str("target features ")?;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
};
|
|
|
|
fmt::from_fn(|f| {
|
|
sub_cfgs
|
|
.iter()
|
|
.map(|sub_cfg| {
|
|
if let CfgEntry::NameValue { value: Some(feat), .. } = sub_cfg
|
|
&& short_longhand
|
|
{
|
|
Either::Left(self.code_wrappers().wrap(feat))
|
|
} else {
|
|
Either::Right(
|
|
Wrapped::with_parens()
|
|
.when(is_any_cfg(sub_cfg))
|
|
.wrap(Display(sub_cfg, self.1)),
|
|
)
|
|
}
|
|
})
|
|
.joined(separator, f)
|
|
})
|
|
.fmt(fmt)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Display<'_> {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match &self.0 {
|
|
CfgEntry::Not(box CfgEntry::Any(sub_cfgs, _), _) => {
|
|
let separator = if sub_cfgs.iter().all(is_simple_cfg) { " nor " } else { ", nor " };
|
|
fmt.write_str("neither ")?;
|
|
|
|
sub_cfgs
|
|
.iter()
|
|
.map(|sub_cfg| {
|
|
Wrapped::with_parens()
|
|
.when(is_any_cfg(sub_cfg))
|
|
.wrap(Display(sub_cfg, self.1))
|
|
})
|
|
.joined(separator, fmt)
|
|
}
|
|
CfgEntry::Not(box simple @ CfgEntry::NameValue { .. }, _) => {
|
|
write!(fmt, "non-{}", Display(simple, self.1))
|
|
}
|
|
CfgEntry::Not(box c, _) => write!(fmt, "not ({})", Display(c, self.1)),
|
|
|
|
CfgEntry::Any(sub_cfgs, _) => {
|
|
let separator = if sub_cfgs.iter().all(is_simple_cfg) { " or " } else { ", or " };
|
|
self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), separator)
|
|
}
|
|
CfgEntry::All(sub_cfgs, _) => self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), " and "),
|
|
|
|
CfgEntry::Bool(v, _) => {
|
|
if *v {
|
|
fmt.write_str("everywhere")
|
|
} else {
|
|
fmt.write_str("nowhere")
|
|
}
|
|
}
|
|
|
|
&CfgEntry::NameValue { name, value, .. } => {
|
|
let human_readable = match (*name, value) {
|
|
(sym::unix, None) => "Unix",
|
|
(sym::windows, None) => "Windows",
|
|
(sym::debug_assertions, None) => "debug-assertions enabled",
|
|
(sym::target_object_format, Some(format)) => match self.1 {
|
|
Format::LongHtml => {
|
|
return write!(fmt, "object format <code>{format}</code>");
|
|
}
|
|
Format::LongPlain => return write!(fmt, "object format `{format}`"),
|
|
Format::ShortHtml => return write!(fmt, "<code>{format}</code>"),
|
|
},
|
|
(sym::target_os, Some(os)) => human_readable_target_os(*os).unwrap_or_default(),
|
|
(sym::target_arch, Some(arch)) => {
|
|
human_readable_target_arch(*arch).unwrap_or_default()
|
|
}
|
|
(sym::target_vendor, Some(vendor)) => match vendor.as_str() {
|
|
"apple" => "Apple",
|
|
"pc" => "PC",
|
|
"sun" => "Sun",
|
|
"fortanix" => "Fortanix",
|
|
_ => "",
|
|
},
|
|
(sym::target_env, Some(env)) => {
|
|
human_readable_target_env(*env).unwrap_or_default()
|
|
}
|
|
(sym::target_endian, Some(endian)) => {
|
|
return write!(fmt, "{endian}-endian");
|
|
}
|
|
(sym::target_pointer_width, Some(bits)) => {
|
|
return write!(fmt, "{bits}-bit");
|
|
}
|
|
(sym::target_feature, Some(feat)) => match self.1 {
|
|
Format::LongHtml => {
|
|
return write!(fmt, "target feature <code>{feat}</code>");
|
|
}
|
|
Format::LongPlain => return write!(fmt, "target feature `{feat}`"),
|
|
Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
|
|
},
|
|
(sym::feature, Some(feat)) => match self.1 {
|
|
Format::LongHtml => {
|
|
return write!(fmt, "crate feature <code>{feat}</code>");
|
|
}
|
|
Format::LongPlain => return write!(fmt, "crate feature `{feat}`"),
|
|
Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
|
|
},
|
|
_ => "",
|
|
};
|
|
if !human_readable.is_empty() {
|
|
fmt.write_str(human_readable)
|
|
} else {
|
|
let value = value
|
|
.map(|v| fmt::from_fn(move |f| write!(f, "={}", self.1.escape(v.as_str()))))
|
|
.maybe_display();
|
|
self.code_wrappers()
|
|
.wrap(format_args!("{}{value}", self.1.escape(name.as_str())))
|
|
.fmt(fmt)
|
|
}
|
|
}
|
|
|
|
CfgEntry::Version(..) => {
|
|
// FIXME: Should we handle it?
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn human_readable_target_os(os: Symbol) -> Option<&'static str> {
|
|
let os = spec::Os::from_str(os.as_str()).ok()?;
|
|
|
|
use spec::Os::*;
|
|
Some(match os {
|
|
// tidy-alphabetical-start
|
|
Aix => "AIX",
|
|
AmdHsa => "AMD HSA",
|
|
Android => "Android",
|
|
Cuda => "CUDA",
|
|
Cygwin => "Cygwin",
|
|
Dragonfly => "DragonFly BSD",
|
|
Emscripten => "Emscripten",
|
|
EspIdf => "ESP-IDF",
|
|
FreeBsd => "FreeBSD",
|
|
Fuchsia => "Fuchsia",
|
|
Haiku => "Haiku",
|
|
HelenOs => "HelenOS",
|
|
Hermit => "Hermit",
|
|
Horizon => "Horizon",
|
|
Hurd => "GNU/Hurd",
|
|
IOs => "iOS",
|
|
Illumos => "illumos",
|
|
L4Re => "L4Re",
|
|
Linux => "Linux",
|
|
LynxOs178 => "LynxOS-178",
|
|
MacOs => "macOS",
|
|
Managarm => "Managarm",
|
|
Motor => "Motor OS",
|
|
NetBsd => "NetBSD",
|
|
None => "bare-metal",
|
|
Nto => "QNX Neutrino",
|
|
NuttX => "NuttX",
|
|
OpenBsd => "OpenBSD",
|
|
Psp => "Play Station Portable",
|
|
Psx => "Play Station 1",
|
|
Qurt => "QuRT",
|
|
Redox => "Redox OS",
|
|
Rtems => "RTEMS OS",
|
|
Solaris => "Solaris",
|
|
SolidAsp3 => "SOLID ASP3",
|
|
TeeOs => "TEEOS",
|
|
Trusty => "Trusty",
|
|
TvOs => "tvOS",
|
|
Uefi => "UEFI",
|
|
VexOs => "VEXos",
|
|
VisionOs => "visionOS",
|
|
Vita => "Play Station Vita",
|
|
VxWorks => "VxWorks",
|
|
Wasi => "WASI",
|
|
WatchOs => "watchOS",
|
|
Windows => "Windows",
|
|
Xous => "Xous",
|
|
Zkvm => "zero knowledge Virtual Machine",
|
|
// tidy-alphabetical-end
|
|
Unknown | Other(_) => return Option::None,
|
|
})
|
|
}
|
|
|
|
fn human_readable_target_arch(os: Symbol) -> Option<&'static str> {
|
|
let arch = spec::Arch::from_str(os.as_str()).ok()?;
|
|
|
|
use spec::Arch::*;
|
|
Some(match arch {
|
|
// tidy-alphabetical-start
|
|
AArch64 => "AArch64",
|
|
AmdGpu => "AMD GPU",
|
|
Arm => "ARM",
|
|
Arm64EC => "ARM64EC",
|
|
Avr => "AVR",
|
|
Bpf => "BPF",
|
|
CSky => "C-SKY",
|
|
Hexagon => "Hexagon",
|
|
LoongArch32 => "LoongArch32",
|
|
LoongArch64 => "LoongArch64",
|
|
M68k => "Motorola 680x0",
|
|
Mips => "MIPS",
|
|
Mips32r6 => "MIPS release 6",
|
|
Mips64 => "MIPS-64",
|
|
Mips64r6 => "MIPS-64 release 6",
|
|
Msp430 => "MSP430",
|
|
Nvptx64 => "NVidia GPU",
|
|
PowerPC => "PowerPC",
|
|
PowerPC64 => "PowerPC64",
|
|
RiscV32 => "RISC-V RV32",
|
|
RiscV64 => "RISC-V RV64",
|
|
S390x => "s390x",
|
|
Sparc => "SPARC",
|
|
Sparc64 => "SPARC-64",
|
|
SpirV => "SPIR-V",
|
|
Wasm32 | Wasm64 => "WebAssembly",
|
|
X86 => "x86",
|
|
X86_64 => "x86-64",
|
|
Xtensa => "Xtensa",
|
|
// tidy-alphabetical-end
|
|
Other(_) => return None,
|
|
})
|
|
}
|
|
|
|
fn human_readable_target_env(env: Symbol) -> Option<&'static str> {
|
|
let env = spec::Env::from_str(env.as_str()).ok()?;
|
|
|
|
use spec::Env::*;
|
|
Some(match env {
|
|
// tidy-alphabetical-start
|
|
Gnu => "GNU",
|
|
MacAbi => "Catalyst",
|
|
Mlibc => "Managarm C Library",
|
|
Msvc => "MSVC",
|
|
Musl => "musl",
|
|
Newlib => "Newlib",
|
|
Nto70 => "Neutrino 7.0",
|
|
Nto71 => "Neutrino 7.1",
|
|
Nto71IoSock => "Neutrino 7.1 with io-sock",
|
|
Nto80 => "Neutrino 8.0",
|
|
Ohos => "OpenHarmony",
|
|
P1 => "WASIp1",
|
|
P2 => "WASIp2",
|
|
P3 => "WASIp3",
|
|
Relibc => "relibc",
|
|
Sgx => "SGX",
|
|
Sim => "Simulator",
|
|
Uclibc => "uClibc",
|
|
V5 => "V5",
|
|
// tidy-alphabetical-end
|
|
Unspecified | Other(_) => return None,
|
|
})
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
struct NameValueCfg {
|
|
name: Symbol,
|
|
value: Option<Symbol>,
|
|
}
|
|
|
|
impl NameValueCfg {
|
|
fn new(name: Symbol) -> Self {
|
|
Self { name, value: None }
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a CfgEntry> for NameValueCfg {
|
|
fn from(cfg: &'a CfgEntry) -> Self {
|
|
match cfg {
|
|
CfgEntry::NameValue { name, value, .. } => NameValueCfg { name: *name, value: *value },
|
|
_ => NameValueCfg { name: sym::empty, value: None },
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a attrs::CfgInfo> for NameValueCfg {
|
|
fn from(cfg: &'a attrs::CfgInfo) -> Self {
|
|
Self { name: cfg.name, value: cfg.value.map(|(value, _)| value) }
|
|
}
|
|
}
|
|
|
|
/// This type keeps track of (doc) cfg information as we go down the item tree.
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct CfgInfo {
|
|
/// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active
|
|
/// `doc(auto_cfg(show(...)))` cfgs.
|
|
hidden_cfg: FxHashSet<NameValueCfg>,
|
|
/// Current computed `cfg`. Each time we enter a new item, this field is updated as well while
|
|
/// taking into account the `hidden_cfg` information.
|
|
current_cfg: Cfg,
|
|
/// Whether the `doc(auto_cfg())` feature is enabled or not at this point.
|
|
auto_cfg_active: bool,
|
|
/// If the parent item used `doc(cfg(...))`, then we don't want to overwrite `current_cfg`,
|
|
/// instead we will concatenate with it. However, if it's not the case, we need to overwrite
|
|
/// `current_cfg`.
|
|
parent_is_doc_cfg: bool,
|
|
}
|
|
|
|
impl Default for CfgInfo {
|
|
fn default() -> Self {
|
|
Self {
|
|
hidden_cfg: FxHashSet::from_iter([
|
|
NameValueCfg::new(sym::test),
|
|
NameValueCfg::new(sym::doc),
|
|
NameValueCfg::new(sym::doctest),
|
|
]),
|
|
current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)),
|
|
auto_cfg_active: true,
|
|
parent_is_doc_cfg: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn show_hide_show_conflict_error(
|
|
tcx: TyCtxt<'_>,
|
|
item_span: rustc_span::Span,
|
|
previous: rustc_span::Span,
|
|
) {
|
|
let mut diag = tcx.sess.dcx().struct_span_err(
|
|
item_span,
|
|
format!(
|
|
"same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item"
|
|
),
|
|
);
|
|
diag.span_note(previous, "first change was here");
|
|
diag.emit();
|
|
}
|
|
|
|
/// This functions updates the `hidden_cfg` field of the provided `cfg_info` argument.
|
|
///
|
|
/// It also checks if a same `cfg` is present in both `auto_cfg(hide(...))` and
|
|
/// `auto_cfg(show(...))` on the same item and emits an error if it's the case.
|
|
///
|
|
/// Because we go through a list of `cfg`s, we keep track of the `cfg`s we saw in `new_show_attrs`
|
|
/// and in `new_hide_attrs` arguments.
|
|
fn handle_auto_cfg_hide_show(
|
|
tcx: TyCtxt<'_>,
|
|
cfg_info: &mut CfgInfo,
|
|
attr: &CfgHideShow,
|
|
new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
|
|
new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
|
|
) {
|
|
for value in &attr.values {
|
|
let simple = NameValueCfg::from(value);
|
|
if attr.kind == HideOrShow::Show {
|
|
if let Some(span) = new_hide_attrs.get(&(simple.name, simple.value)) {
|
|
show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span);
|
|
} else {
|
|
new_show_attrs.insert((simple.name, simple.value), value.span_for_name_and_value());
|
|
}
|
|
cfg_info.hidden_cfg.remove(&simple);
|
|
} else {
|
|
if let Some(span) = new_show_attrs.get(&(simple.name, simple.value)) {
|
|
show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span);
|
|
} else {
|
|
new_hide_attrs.insert((simple.name, simple.value), value.span_for_name_and_value());
|
|
}
|
|
cfg_info.hidden_cfg.insert(simple);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute> + Clone>(
|
|
attrs: I,
|
|
tcx: TyCtxt<'_>,
|
|
cfg_info: &mut CfgInfo,
|
|
) -> Option<Arc<Cfg>> {
|
|
fn check_changed_auto_active_status(
|
|
changed_auto_active_status: &mut Option<rustc_span::Span>,
|
|
attr_span: Span,
|
|
cfg_info: &mut CfgInfo,
|
|
tcx: TyCtxt<'_>,
|
|
new_value: bool,
|
|
) -> bool {
|
|
if let Some(first_change) = changed_auto_active_status {
|
|
if cfg_info.auto_cfg_active != new_value {
|
|
tcx.sess
|
|
.dcx()
|
|
.struct_span_err(
|
|
vec![*first_change, attr_span],
|
|
"`auto_cfg` was disabled and enabled more than once on the same item",
|
|
)
|
|
.emit();
|
|
return true;
|
|
}
|
|
} else {
|
|
*changed_auto_active_status = Some(attr_span);
|
|
}
|
|
cfg_info.auto_cfg_active = new_value;
|
|
false
|
|
}
|
|
|
|
let mut new_show_attrs = FxHashMap::default();
|
|
let mut new_hide_attrs = FxHashMap::default();
|
|
|
|
let mut doc_cfg = attrs
|
|
.clone()
|
|
.filter_map(|attr| match attr {
|
|
Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => Some(d),
|
|
_ => None,
|
|
})
|
|
.peekable();
|
|
// If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes.
|
|
if doc_cfg.peek().is_some() {
|
|
// We overwrite existing `cfg`.
|
|
if !cfg_info.parent_is_doc_cfg {
|
|
cfg_info.current_cfg = Cfg(CfgEntry::Bool(true, DUMMY_SP));
|
|
cfg_info.parent_is_doc_cfg = true;
|
|
}
|
|
for attr in doc_cfg {
|
|
for new_cfg in attr.cfg.clone() {
|
|
cfg_info.current_cfg &= Cfg(new_cfg);
|
|
}
|
|
}
|
|
} else {
|
|
cfg_info.parent_is_doc_cfg = false;
|
|
}
|
|
|
|
let mut changed_auto_active_status = None;
|
|
|
|
// We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes.
|
|
for attr in attrs {
|
|
if let Attribute::Parsed(AttributeKind::Doc(d)) = attr {
|
|
for (new_value, span) in &d.auto_cfg_change {
|
|
if check_changed_auto_active_status(
|
|
&mut changed_auto_active_status,
|
|
*span,
|
|
cfg_info,
|
|
tcx,
|
|
*new_value,
|
|
) {
|
|
return None;
|
|
}
|
|
}
|
|
if let Some((_, span)) = d.auto_cfg.first() {
|
|
if check_changed_auto_active_status(
|
|
&mut changed_auto_active_status,
|
|
*span,
|
|
cfg_info,
|
|
tcx,
|
|
true,
|
|
) {
|
|
return None;
|
|
}
|
|
for (value, _) in &d.auto_cfg {
|
|
handle_auto_cfg_hide_show(
|
|
tcx,
|
|
cfg_info,
|
|
value,
|
|
&mut new_show_attrs,
|
|
&mut new_hide_attrs,
|
|
);
|
|
}
|
|
}
|
|
} else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr {
|
|
// Treat `#[target_feature(enable = "feat")]` attributes as if they were
|
|
// `#[doc(cfg(target_feature = "feat"))]` attributes as well.
|
|
for (feature, _) in features {
|
|
cfg_info.current_cfg &= Cfg(CfgEntry::NameValue {
|
|
name: sym::target_feature,
|
|
value: Some(*feature),
|
|
span: DUMMY_SP,
|
|
});
|
|
}
|
|
continue;
|
|
} else if !cfg_info.parent_is_doc_cfg
|
|
&& let hir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr
|
|
{
|
|
for (new_cfg, _) in cfgs {
|
|
cfg_info.current_cfg &= Cfg(new_cfg.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
// If `doc(auto_cfg)` feature is disabled and `doc(cfg())` wasn't used, there is nothing
|
|
// to be done here.
|
|
if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg {
|
|
None
|
|
} else if cfg_info.parent_is_doc_cfg {
|
|
if matches!(cfg_info.current_cfg.0, CfgEntry::Bool(true, _)) {
|
|
None
|
|
} else {
|
|
Some(Arc::new(cfg_info.current_cfg.clone()))
|
|
}
|
|
} else {
|
|
// If `doc(auto_cfg)` feature is enabled, we want to collect all `cfg` items, we remove the
|
|
// hidden ones afterward.
|
|
match strip_hidden(&cfg_info.current_cfg.0, &cfg_info.hidden_cfg) {
|
|
None | Some(CfgEntry::Bool(true, _)) => None,
|
|
Some(cfg) => Some(Arc::new(Cfg(cfg))),
|
|
}
|
|
}
|
|
}
|