Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
df007cf
rewrite doc attribute (non-doc-comments)
jdonszelmann Jun 20, 2025
3f08e4d
start using parsed doc attributes everywhere
jdonszelmann Jun 22, 2025
acb32df
Continue migration to new Attribute API for `doc` attribute
GuillaumeGomez Oct 23, 2025
131323f
Migrate `doc(cfg)` to new `Attribute` API
GuillaumeGomez Nov 25, 2025
04bcd83
Move `doc = ""` parsing out of `DocParser` to keep doc attributes order
GuillaumeGomez Nov 27, 2025
148e522
Correctly differentiate between sugared and raw doc comments
GuillaumeGomez Nov 28, 2025
92edc49
Correctly handle doc items inlining
GuillaumeGomez Nov 28, 2025
368a103
Fix `Cfg` add/or operations
GuillaumeGomez Nov 28, 2025
d35ec31
Keep a list of `CfgEntry` instead of just one
GuillaumeGomez Dec 1, 2025
57870b7
Fix `doc(auto_cfg)` attribute parsing
GuillaumeGomez Dec 1, 2025
5c47240
Correctly handle `doc(test(attr(...)))`
GuillaumeGomez Dec 2, 2025
976d454
Update rustdoc unit tests
GuillaumeGomez Dec 2, 2025
9c8c67b
Fix warning messages
GuillaumeGomez Dec 3, 2025
d1277cc
Update `check_doc_cfg` pass in rustdoc, remove old `rustc_attr_parsin…
GuillaumeGomez Dec 4, 2025
e4f57dd
Finish fixing ui tests
GuillaumeGomez Dec 4, 2025
3fa499b
Sort fluent messages
GuillaumeGomez Dec 4, 2025
a36c462
Update clippy code to new doc attribute API
GuillaumeGomez Dec 4, 2025
aa3bf6f
Update rustc_resolve unit tests
GuillaumeGomez Dec 4, 2025
4936973
Fix ui tests
GuillaumeGomez Dec 5, 2025
348d9d9
Correctly iterate doc comments in intra-doc resolution in `rustc_reso…
GuillaumeGomez Dec 5, 2025
8d4f59b
Clean up code
GuillaumeGomez Dec 5, 2025
2340f80
Update to new lint API
GuillaumeGomez Dec 6, 2025
06238bd
Update rustdoc JSON output to new attribute API
GuillaumeGomez Dec 6, 2025
1da7684
Improve code and add more comments
GuillaumeGomez Dec 7, 2025
4191e94
Improve spans for `auto_cfg(hide/show)` errors
GuillaumeGomez Dec 8, 2025
6230b56
Update clippy code
GuillaumeGomez Dec 8, 2025
40907f5
Fix doc alias suggestion
GuillaumeGomez Dec 8, 2025
64aaeac
Update to new API, allowing to remove `check_doc_cfg.rs` file from li…
GuillaumeGomez Dec 9, 2025
3ea9462
Remove `Cfg::matches` and use `eval_config_entry` instead
GuillaumeGomez Dec 10, 2025
9fdec81
Fix new merge conflict
GuillaumeGomez Dec 10, 2025
2bc2a0d
For now, ignore target checking for doc attributes in attr_parsing
GuillaumeGomez Dec 10, 2025
5180632
Do not error if there are duplicated doc attributes
GuillaumeGomez Dec 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix doc(auto_cfg) attribute parsing
  • Loading branch information
GuillaumeGomez committed Dec 10, 2025
commit 57870b7242e3b3446abad0bc1672b52a46d92e1f
24 changes: 18 additions & 6 deletions compiler/rustc_attr_parsing/src/attributes/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,21 @@ impl DocParser {
cx: &'c mut AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
) {
if let Some(cfg_entry) = super::cfg::parse_cfg(cx, args) {
// This function replaces cases like `cfg(all())` with `true`.
fn simplify_cfg(cfg_entry: &mut CfgEntry) {
match cfg_entry {
CfgEntry::All(cfgs, span) if cfgs.is_empty() => {
*cfg_entry = CfgEntry::Bool(true, *span)
}
CfgEntry::Any(cfgs, span) if cfgs.is_empty() => {
*cfg_entry = CfgEntry::Bool(false, *span)
}
CfgEntry::Not(cfg, _) => simplify_cfg(cfg),
_ => {}
}
}
if let Some(mut cfg_entry) = super::cfg::parse_cfg(cx, args) {
simplify_cfg(&mut cfg_entry);
self.attribute.cfg.push(cfg_entry);
}
}
Expand All @@ -237,7 +251,7 @@ impl DocParser {
) {
match args {
ArgParser::NoArgs => {
cx.expected_list(args.span().unwrap_or(path.span()));
self.attribute.auto_cfg_change.push((true, path.span()));
}
ArgParser::List(list) => {
for meta in list.mixed() {
Expand Down Expand Up @@ -303,6 +317,7 @@ impl DocParser {
}
}
}
self.attribute.auto_cfg.push((cfg_hide_show, path.span()));
}
}
ArgParser::NameValue(nv) => {
Expand All @@ -311,7 +326,7 @@ impl DocParser {
cx.emit_lint(AttributeLintKind::DocAutoCfgWrongLiteral, nv.value_span);
return;
};
self.attribute.auto_cfg_change = Some((*bool_value, *span));
self.attribute.auto_cfg_change.push((*bool_value, *span));
}
}
}
Expand Down Expand Up @@ -524,9 +539,6 @@ impl<S: Stage> AttributeParser<S> for DocParser {

fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
if self.nb_doc_attrs != 0 {
if std::env::var("LOL").is_ok() {
eprintln!("+++++> {:#?}", self.attribute);
}
Some(AttributeKind::Doc(Box::new(self.attribute)))
} else {
None
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,9 @@ pub struct DocAttribute {

// unstable
pub cfg: ThinVec<CfgEntry>,
pub auto_cfg: ThinVec<CfgHideShow>,
/// This is for `#[doc(auto_cfg = false|true)]`.
pub auto_cfg_change: Option<(bool, Span)>,
pub auto_cfg: ThinVec<(CfgHideShow, Span)>,
/// This is for `#[doc(auto_cfg = false|true)]`/`#[doc(auto_cfg)]`.
pub auto_cfg_change: ThinVec<(bool, Span)>,

// builtin
pub fake_variadic: Option<Span>,
Expand Down Expand Up @@ -514,7 +514,7 @@ impl Default for DocAttribute {
inline: None,
cfg: ThinVec::new(),
auto_cfg: ThinVec::new(),
auto_cfg_change: None,
auto_cfg_change: ThinVec::new(),
fake_variadic: None,
keyword: None,
attribute: None,
Expand Down
151 changes: 60 additions & 91 deletions src/librustdoc/clean/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ 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;
use rustc_hir::Attribute;
use rustc_hir::attrs::{AttributeKind, CfgEntry};
use rustc_hir::attrs::{self, AttributeKind, CfgEntry, CfgHideShow, HideOrShow};
use rustc_middle::ty::TyCtxt;
use rustc_session::parse::ParseSess;
use rustc_span::symbol::{Symbol, sym};
use rustc_span::{DUMMY_SP, Span};
use {rustc_ast as ast, rustc_hir as hir};

use crate::display::{Joined as _, MaybeDisplay, Wrapped};
use crate::html::escape::Escape;
Expand Down Expand Up @@ -689,6 +689,12 @@ impl<'a> From<&'a CfgEntry> for SimpleCfg {
}
}

impl<'a> From<&'a attrs::CfgInfo> for SimpleCfg {
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 {
Expand Down Expand Up @@ -746,37 +752,27 @@ fn show_hide_show_conflict_error(
fn handle_auto_cfg_hide_show(
tcx: TyCtxt<'_>,
cfg_info: &mut CfgInfo,
sub_attr: &MetaItemInner,
is_show: bool,
attr: &CfgHideShow,
attr_span: Span,
new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
) {
if let MetaItemInner::MetaItem(item) = sub_attr
&& let MetaItemKind::List(items) = &item.kind
{
for item in items {
// FIXME: Report in case `Cfg::parse` reports an error?

let Ok(cfg) = Cfg::parse(item) else { continue };
if let CfgEntry::NameValue { name, value, .. } = cfg.0 {
let value = value.map(|(v, _)| v);
let simple = SimpleCfg::from(&cfg.0);
if is_show {
if let Some(span) = new_hide_attrs.get(&(name, value)) {
show_hide_show_conflict_error(tcx, item.span(), *span);
} else {
new_show_attrs.insert((name, value), item.span());
}
cfg_info.hidden_cfg.remove(&simple);
} else {
if let Some(span) = new_show_attrs.get(&(name, value)) {
show_hide_show_conflict_error(tcx, item.span(), *span);
} else {
new_hide_attrs.insert((name, value), item.span());
}
cfg_info.hidden_cfg.insert(simple);
}
for value in &attr.values {
let simple = SimpleCfg::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, attr_span, *span);
} else {
new_show_attrs.insert((simple.name, simple.value), attr_span);
}
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, attr_span, *span);
} else {
new_hide_attrs.insert((simple.name, simple.value), attr_span);
}
cfg_info.hidden_cfg.insert(simple);
}
}
}
Expand All @@ -797,7 +793,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>

fn check_changed_auto_active_status(
changed_auto_active_status: &mut Option<rustc_span::Span>,
attr: &ast::MetaItem,
attr_span: Span,
cfg_info: &mut CfgInfo,
tcx: TyCtxt<'_>,
new_value: bool,
Expand All @@ -807,14 +803,14 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
tcx.sess
.dcx()
.struct_span_err(
vec![*first_change, attr.span],
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);
*changed_auto_active_status = Some(attr_span);
}
cfg_info.auto_cfg_active = new_value;
false
Expand All @@ -826,7 +822,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
let mut doc_cfg = attrs
.clone()
.filter_map(|attr| match attr {
Attribute::Parsed(AttributeKind::Doc(d)) => Some(d),
Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => Some(d),
_ => None,
})
.peekable();
Expand All @@ -850,64 +846,37 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>

// We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes.
for attr in attrs {
if let Some(ident) = attr.ident()
&& ident.name == sym::doc
&& let Some(attrs) = attr.meta_item_list()
{
for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) {
let MetaItemInner::MetaItem(attr) = attr else {
continue;
};
match &attr.kind {
MetaItemKind::Word => {
if check_changed_auto_active_status(
&mut changed_auto_active_status,
attr,
cfg_info,
tcx,
true,
) {
return None;
}
}
MetaItemKind::NameValue(lit) => {
if let LitKind::Bool(value) = lit.kind {
if check_changed_auto_active_status(
&mut changed_auto_active_status,
attr,
cfg_info,
tcx,
value,
) {
return None;
}
}
}
MetaItemKind::List(sub_attrs) => {
if check_changed_auto_active_status(
&mut changed_auto_active_status,
attr,
cfg_info,
tcx,
true,
) {
return None;
}
for sub_attr in sub_attrs.iter() {
if let Some(ident) = sub_attr.ident()
&& (ident.name == sym::show || ident.name == sym::hide)
{
handle_auto_cfg_hide_show(
tcx,
cfg_info,
&sub_attr,
ident.name == sym::show,
&mut new_show_attrs,
&mut new_hide_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, span) in &d.auto_cfg {
handle_auto_cfg_hide_show(
tcx,
cfg_info,
value,
*span,
&mut new_show_attrs,
&mut new_hide_attrs,
);
}
}
} else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr {
Expand Down