Convert librustdoc to use the new parsed representation

This commit is contained in:
Jonathan Brouwer 2025-12-26 20:57:26 +01:00
parent 5590fc034c
commit 4429814412
No known key found for this signature in database
GPG key ID: F13E55D38C971DEF
4 changed files with 11 additions and 273 deletions

View file

@ -7,7 +7,6 @@ use std::sync::Arc;
use std::{fmt, mem, ops};
use itertools::Either;
use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::thin_vec::{ThinVec, thin_vec};
use rustc_hir as hir;
@ -29,12 +28,6 @@ mod tests;
#[cfg_attr(test, derive(PartialEq))]
pub(crate) struct Cfg(CfgEntry);
#[derive(PartialEq, Debug)]
pub(crate) struct InvalidCfgError {
pub(crate) msg: &'static str,
pub(crate) span: Span,
}
/// Whether the configuration consists of just `Cfg` or `Not`.
fn is_simple_cfg(cfg: &CfgEntry) -> bool {
match cfg {
@ -105,106 +98,6 @@ fn should_capitalize_first_letter(cfg: &CfgEntry) -> bool {
}
impl Cfg {
/// Parses a `MetaItemInner` into a `Cfg`.
fn parse_nested(
nested_cfg: &MetaItemInner,
exclude: &FxHashSet<NameValueCfg>,
) -> Result<Option<Cfg>, InvalidCfgError> {
match nested_cfg {
MetaItemInner::MetaItem(cfg) => Cfg::parse_without(cfg, exclude),
MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => {
Ok(Some(Cfg(CfgEntry::Bool(*b, DUMMY_SP))))
}
MetaItemInner::Lit(lit) => {
Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
}
}
}
fn parse_without(
cfg: &MetaItem,
exclude: &FxHashSet<NameValueCfg>,
) -> Result<Option<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 => {
if exclude.contains(&NameValueCfg::new(name)) {
Ok(None)
} else {
Ok(Some(Cfg(CfgEntry::NameValue { name, value: None, span: DUMMY_SP })))
}
}
MetaItemKind::NameValue(ref lit) => match lit.kind {
LitKind::Str(value, _) => {
if exclude.contains(&NameValueCfg::new_value(name, value)) {
Ok(None)
} else {
Ok(Some(Cfg(CfgEntry::NameValue {
name,
value: Some(value),
span: DUMMY_SP,
})))
}
}
_ => 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 orig_len = items.len();
let mut sub_cfgs =
items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose());
let ret = match name {
sym::all => {
sub_cfgs.try_fold(Cfg(CfgEntry::Bool(true, DUMMY_SP)), |x, y| Ok(x & y?))
}
sym::any => {
sub_cfgs.try_fold(Cfg(CfgEntry::Bool(false, DUMMY_SP)), |x, y| Ok(x | y?))
}
sym::not => {
if orig_len == 1 {
let mut sub_cfgs = sub_cfgs.collect::<Vec<_>>();
if sub_cfgs.len() == 1 {
Ok(!sub_cfgs.pop().unwrap()?)
} else {
return Ok(None);
}
} else {
Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span })
}
}
_ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }),
};
match ret {
Ok(c) => Ok(Some(c)),
Err(e) => Err(e),
}
}
}
}
/// 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(crate) fn parse(cfg: &MetaItemInner) -> Result<Cfg, InvalidCfgError> {
Self::parse_nested(cfg, &FxHashSet::default()).map(|ret| ret.unwrap())
}
/// 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();
@ -644,10 +537,6 @@ impl NameValueCfg {
fn new(name: Symbol) -> Self {
Self { name, value: None }
}
fn new_value(name: Symbol, value: Symbol) -> Self {
Self { name, value: Some(value) }
}
}
impl<'a> From<&'a CfgEntry> for NameValueCfg {
@ -751,15 +640,6 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
tcx: TyCtxt<'_>,
cfg_info: &mut CfgInfo,
) -> Option<Arc<Cfg>> {
fn single<T: IntoIterator>(it: T) -> Option<T::Item> {
let mut iter = it.into_iter();
let item = iter.next()?;
if iter.next().is_some() {
return None;
}
Some(item)
}
fn check_changed_auto_active_status(
changed_auto_active_status: &mut Option<rustc_span::Span>,
attr_span: Span,
@ -859,12 +739,11 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
}
continue;
} else if !cfg_info.parent_is_doc_cfg
&& let Some(name) = attr.name()
&& matches!(name, sym::cfg | sym::cfg_trace)
&& let Some(attr) = single(attr.meta_item_list()?)
&& let Ok(new_cfg) = Cfg::parse(&attr)
&& let hir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr
{
cfg_info.current_cfg &= new_cfg;
for (new_cfg, _) in cfgs {
cfg_info.current_cfg &= Cfg(new_cfg.clone());
}
}
}

View file

@ -1,8 +1,5 @@
use rustc_ast::ast::LitIntType;
use rustc_ast::{MetaItemInner, MetaItemLit, Path, Safety, StrStyle};
use rustc_data_structures::thin_vec::thin_vec;
use rustc_hir::attrs::CfgEntry;
use rustc_span::symbol::{Ident, kw};
use rustc_span::{DUMMY_SP, create_default_session_globals_then};
use super::*;
@ -28,10 +25,6 @@ fn name_value_cfg_e(name: &str, value: &str) -> CfgEntry {
}
}
fn dummy_lit(symbol: Symbol, kind: LitKind) -> MetaItemInner {
MetaItemInner::Lit(MetaItemLit { symbol, suffix: None, kind, span: DUMMY_SP })
}
fn cfg_all(v: ThinVec<CfgEntry>) -> Cfg {
Cfg(cfg_all_e(v))
}
@ -52,51 +45,6 @@ fn cfg_not(v: CfgEntry) -> Cfg {
Cfg(CfgEntry::Not(Box::new(v), DUMMY_SP))
}
fn dummy_meta_item_word(name: &str) -> MetaItemInner {
MetaItemInner::MetaItem(MetaItem {
unsafety: Safety::Default,
path: Path::from_ident(Ident::from_str(name)),
kind: MetaItemKind::Word,
span: DUMMY_SP,
})
}
fn dummy_meta_item_name_value(name: &str, symbol: Symbol, kind: LitKind) -> MetaItemInner {
let lit = MetaItemLit { symbol, suffix: None, kind, span: DUMMY_SP };
MetaItemInner::MetaItem(MetaItem {
unsafety: Safety::Default,
path: Path::from_ident(Ident::from_str(name)),
kind: MetaItemKind::NameValue(lit),
span: DUMMY_SP,
})
}
macro_rules! dummy_meta_item_list {
($name:ident, [$($list:ident),* $(,)?]) => {
MetaItemInner::MetaItem(MetaItem {
unsafety: Safety::Default,
path: Path::from_ident(Ident::from_str(stringify!($name))),
kind: MetaItemKind::List(thin_vec![
$(
dummy_meta_item_word(stringify!($list)),
)*
]),
span: DUMMY_SP,
})
};
($name:ident, [$($list:expr),* $(,)?]) => {
MetaItemInner::MetaItem(MetaItem {
unsafety: Safety::Default,
path: Path::from_ident(Ident::from_str(stringify!($name))),
kind: MetaItemKind::List(thin_vec![
$($list,)*
]),
span: DUMMY_SP,
})
};
}
fn cfg_true() -> Cfg {
Cfg(CfgEntry::Bool(true, DUMMY_SP))
}
@ -303,87 +251,6 @@ fn test_cfg_or() {
})
}
#[test]
fn test_parse_ok() {
create_default_session_globals_then(|| {
let r#true = Symbol::intern("true");
let mi = dummy_lit(r#true, LitKind::Bool(true));
assert_eq!(Cfg::parse(&mi), Ok(cfg_true()));
let r#false = Symbol::intern("false");
let mi = dummy_lit(r#false, LitKind::Bool(false));
assert_eq!(Cfg::parse(&mi), Ok(cfg_false()));
let mi = dummy_meta_item_word("all");
assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all")));
let done = Symbol::intern("done");
let mi = dummy_meta_item_name_value("all", done, LitKind::Str(done, StrStyle::Cooked));
assert_eq!(Cfg::parse(&mi), Ok(name_value_cfg("all", "done")));
let mi = dummy_meta_item_list!(all, [a, b]);
assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b")));
let mi = dummy_meta_item_list!(any, [a, b]);
assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") | word_cfg("b")));
let mi = dummy_meta_item_list!(not, [a]);
assert_eq!(Cfg::parse(&mi), Ok(!word_cfg("a")));
let mi = dummy_meta_item_list!(
not,
[dummy_meta_item_list!(
any,
[dummy_meta_item_word("a"), dummy_meta_item_list!(all, [b, c]),]
),]
);
assert_eq!(Cfg::parse(&mi), Ok(!(word_cfg("a") | (word_cfg("b") & word_cfg("c")))));
let mi = dummy_meta_item_list!(all, [a, b, c]);
assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b") & word_cfg("c")));
})
}
#[test]
fn test_parse_err() {
create_default_session_globals_then(|| {
let mi = dummy_meta_item_name_value("foo", kw::False, LitKind::Bool(false));
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(not, [a, b]);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(not, []);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(foo, []);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(
all,
[dummy_meta_item_list!(foo, []), dummy_meta_item_word("b"),]
);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(
any,
[dummy_meta_item_word("a"), dummy_meta_item_list!(foo, []),]
);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(not, [dummy_meta_item_list!(foo, []),]);
assert!(Cfg::parse(&mi).is_err());
let c = Symbol::intern("e");
let mi = dummy_lit(c, LitKind::Char('e'));
assert!(Cfg::parse(&mi).is_err());
let five = Symbol::intern("5");
let mi = dummy_lit(five, LitKind::Int(5.into(), LitIntType::Unsuffixed));
assert!(Cfg::parse(&mi).is_err());
})
}
#[test]
fn test_render_short_html() {
create_default_session_globals_then(|| {

View file

@ -51,7 +51,7 @@ use rustc_middle::ty::{self, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeVisitableE
use rustc_middle::{bug, span_bug};
use rustc_span::ExpnKind;
use rustc_span::hygiene::{AstPass, MacroKind};
use rustc_span::symbol::{Ident, Symbol, kw, sym};
use rustc_span::symbol::{Ident, Symbol, kw};
use rustc_trait_selection::traits::wf::object_region_bounds;
use tracing::{debug, instrument};
use utils::*;
@ -2682,17 +2682,13 @@ fn add_without_unwanted_attributes<'hir>(
import_parent,
));
}
hir::Attribute::Unparsed(normal) if let [name] = &*normal.path.segments => {
if is_inline || *name != sym::cfg_trace {
// If it's not a `cfg()` attribute, we keep it.
attrs.push((Cow::Borrowed(attr), import_parent));
}
}
// FIXME: make sure to exclude `#[cfg_trace]` here when it is ported to the new parsers
hir::Attribute::Parsed(..) => {
// We discard `#[cfg(...)]` attributes unless we're inlining
hir::Attribute::Parsed(AttributeKind::CfgTrace(..)) if !is_inline => {}
// We keep all other attributes
_ => {
attrs.push((Cow::Borrowed(attr), import_parent));
}
_ => {}
}
}
}

View file

@ -2,7 +2,6 @@
use rustc_hir::Attribute;
use rustc_hir::attrs::{AttributeKind, DocAttribute};
use rustc_span::symbol::sym;
use crate::clean::inline::{load_attrs, merge_attrs};
use crate::clean::{CfgInfo, Crate, Item, ItemKind};
@ -39,10 +38,7 @@ fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute])
let mut new_attr = DocAttribute::default();
new_attr.cfg = d.cfg.clone();
attrs.push(Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr))));
} else if let Attribute::Unparsed(normal) = attr
&& let [name] = &*normal.path.segments
&& *name == sym::cfg_trace
{
} else if let Attribute::Parsed(AttributeKind::CfgTrace(..)) = attr {
// If it's a `cfg()` attribute, we keep it.
attrs.push(attr.clone());
}