Skip to content

Commit eb804b3

Browse files
Rollup merge of #157871 - GuillaumeGomez:doc_cfg-syntax, r=Urgau
[rustdoc] Update `doc_cfg` hide/show syntax Part of #43781. As discussed at the all-hands with the rustdoc team, we decided to follow the `check-cfg` syntax. So this PR implements it. Considering @Urgau made the `check-cfg` implementation, I'll set them as reviewer here. r? @Urgau
2 parents 267c405 + 702445c commit eb804b3

35 files changed

Lines changed: 747 additions & 252 deletions

compiler/rustc_attr_parsing/src/attributes/doc.rs

Lines changed: 133 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
2+
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};
23
use rustc_errors::{Applicability, msg};
34
use rustc_feature::AttributeStability;
45
use rustc_hir::Target;
56
use rustc_hir::attrs::{
6-
AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow,
7+
AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue,
8+
DocInline, HideOrShow,
79
};
810
use rustc_session::errors::feature_err;
911
use rustc_span::{Span, Symbol, edition, sym};
10-
use thin_vec::ThinVec;
1112

1213
use super::prelude::{ALL_TARGETS, AllowedTargets};
1314
use super::{AcceptMapping, AttributeParser, template};
1415
use crate::context::{AcceptContext, FinalizeContext};
1516
use crate::diagnostics::{
1617
AttrCrateLevelOnly, DocAliasDuplicated, DocAutoCfgExpectsHideOrShow,
17-
DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowUnexpectedItem, DocAutoCfgWrongLiteral,
18-
DocTestLiteral, DocTestTakesList, DocTestUnknown, DocUnknownAny, DocUnknownInclude,
19-
DocUnknownPasses, DocUnknownPlugins, DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs,
20-
IllFormedAttributeInput, MalformedDoc,
18+
DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues,
19+
DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues,
20+
DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocTestLiteral, DocTestTakesList,
21+
DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins,
22+
DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput, MalformedDoc,
23+
};
24+
use crate::parser::{
25+
ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser,
2126
};
22-
use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser};
2327
use crate::session_diagnostics::{
2428
DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttrNotCrateLevel,
2529
DocAttributeNotAttribute, DocKeywordNotKeyword, UnusedDuplicate,
@@ -304,6 +308,81 @@ impl DocParser {
304308
}
305309
}
306310

311+
// Parses the `doc(auto_cfg(hide/show(..., values())))` attribute.
312+
fn parse_auto_cfg_values(
313+
&self,
314+
cx: &mut AcceptContext<'_, '_>,
315+
list: &MetaItemListParser,
316+
values: &mut Option<DocCfgHideShow>,
317+
) {
318+
let mut cfg_values = DocCfgHideShow::new();
319+
320+
let mut values_set = FxHashSet::default();
321+
for item in list.mixed() {
322+
match item {
323+
// If it's a string literal, all good.
324+
MetaItemOrLitParser::Lit(MetaItemLit {
325+
kind: LitKind::Str(symbol, _),
326+
span,
327+
..
328+
}) => match &mut cfg_values {
329+
DocCfgHideShow::Any(any_span) => {
330+
cx.emit_lint(
331+
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
332+
DocAutoCfgHideShowValuesMix { value_span: *span },
333+
*any_span,
334+
);
335+
}
336+
DocCfgHideShow::List(symbols) => {
337+
if values_set.insert(symbol) {
338+
symbols.push(DocCfgHideShowValue::new(*symbol, *span));
339+
}
340+
}
341+
},
342+
// If it's any other kind of literal, then it's wrong and we emit a lint.
343+
MetaItemOrLitParser::Lit(lit) => cx.emit_lint(
344+
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
345+
DocAutoCfgHideShowUnexpectedItem { attr_name: lit.symbol },
346+
lit.span,
347+
),
348+
// If it's a list, then only `any()` and `none()` are allowed and they must not
349+
// contain any item.
350+
MetaItemOrLitParser::MetaItemParser(sub_item) => {
351+
if let Some(ident) = sub_item.ident()
352+
&& [sym::any, sym::none].contains(&ident.name)
353+
&& let ArgParser::List(list) = sub_item.args()
354+
&& list.mixed().count() == 0
355+
{
356+
if ident.name == sym::any {
357+
if let DocCfgHideShow::List(values) = &cfg_values
358+
&& let Some(value) = values.first()
359+
{
360+
cx.emit_lint(
361+
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
362+
DocAutoCfgHideShowValuesMix { value_span: value.span },
363+
sub_item.span(),
364+
);
365+
} else {
366+
cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span()));
367+
}
368+
} else {
369+
cfg_values.push_none(sub_item.span());
370+
}
371+
} else {
372+
cx.emit_lint(
373+
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
374+
DocAutoCfgHideShowUnexpectedItem {
375+
attr_name: sub_item.ident().unwrap().name,
376+
},
377+
sub_item.span(),
378+
);
379+
}
380+
}
381+
}
382+
}
383+
*values = Some(cfg_values);
384+
}
385+
307386
fn parse_auto_cfg(
308387
&mut self,
309388
cx: &mut AcceptContext<'_, '_>,
@@ -315,7 +394,7 @@ impl DocParser {
315394
self.attribute.auto_cfg_change.push((true, path.span()));
316395
}
317396
ArgParser::List(list) => {
318-
for meta in list.mixed() {
397+
'main: for meta in list.mixed() {
319398
let MetaItemOrLitParser::MetaItemParser(item) = meta else {
320399
cx.emit_lint(
321400
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
@@ -324,6 +403,8 @@ impl DocParser {
324403
);
325404
continue;
326405
};
406+
// Only `hide` and `show` are allowed in `auto_cfg` if it's a list, and both
407+
// must be a list.
327408
let (kind, attr_name) = match item.path().word_sym() {
328409
Some(sym::hide) => (HideOrShow::Hide, sym::hide),
329410
Some(sym::show) => (HideOrShow::Show, sym::show),
@@ -345,56 +426,71 @@ impl DocParser {
345426
continue;
346427
};
347428

348-
let mut cfg_hide_show = CfgHideShow { kind, values: ThinVec::new() };
429+
let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() };
349430

431+
let mut cfg_names = FxHashSet::default();
432+
let mut values = None;
350433
for item in list.mixed() {
351434
let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
352435
cx.emit_lint(
353436
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
354437
DocAutoCfgHideShowUnexpectedItem { attr_name },
355438
item.span(),
356439
);
357-
continue;
440+
continue 'main;
358441
};
359442
match sub_item.args() {
360-
a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
443+
ArgParser::NoArgs if values.is_none() => {
361444
let Some(name) = sub_item.path().word_sym() else {
362-
// FIXME: remove this method once merged and uncomment the line
363-
// below instead.
364-
// cx.expected_identifier(sub_item.path().span());
445+
cx.adcx().expected_identifier(sub_item.path().span());
446+
continue 'main;
447+
};
448+
cfg_names.insert(name);
449+
}
450+
// The only accepted list is `values()`.
451+
ArgParser::List(list) if values.is_none() => {
452+
let Some(sym::values) = sub_item.path().word_sym() else {
453+
cx.adcx().expected_identifier(sub_item.path().span());
454+
continue 'main;
455+
};
456+
if cfg_names.is_empty() {
365457
cx.emit_lint(
366458
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
367-
MalformedDoc,
368-
sub_item.path().span(),
369-
);
370-
continue;
371-
};
372-
if let Ok(CfgEntry::NameValue { name, value, .. }) =
373-
super::cfg::parse_name_value(
374-
name,
375-
sub_item.path().span(),
376-
a.as_name_value(),
459+
DocAutoCfgHideShowNoIdentBeforeValues,
377460
sub_item.span(),
378-
cx,
379-
)
380-
{
381-
cfg_hide_show.values.push(CfgInfo {
382-
name,
383-
name_span: sub_item.path().span(),
384-
// If `value` is `Some`, `a.name_value()` will always return
385-
// `Some` as well.
386-
value: value
387-
.map(|v| (v, a.as_name_value().unwrap().value_span)),
388-
})
461+
);
462+
continue 'main;
389463
}
464+
self.parse_auto_cfg_values(cx, list, &mut values);
390465
}
391-
_ => {
466+
// No `name = value` is allowed.
467+
ArgParser::NameValue(_) => {
392468
cx.emit_lint(
393469
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
394470
DocAutoCfgHideShowUnexpectedItem { attr_name },
395471
sub_item.span(),
396472
);
397-
continue;
473+
}
474+
// If `values()` was already used, no item should come after it.
475+
_ => {
476+
cx.emit_lint(
477+
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
478+
DocAutoCfgHideShowUnexpectedItemAfterValues,
479+
sub_item.span(),
480+
);
481+
}
482+
}
483+
}
484+
485+
let values = values.unwrap_or(DocCfgHideShow::new_with_only_key(item.span()));
486+
#[allow(rustc::potential_query_instability)]
487+
for cfg_name in &cfg_names {
488+
match cfg_hide_show.values.entry(*cfg_name) {
489+
IndexEntry::Vacant(v) => {
490+
v.insert(values.clone());
491+
}
492+
IndexEntry::Occupied(mut o) => {
493+
o.get_mut().merge_with(&values);
398494
}
399495
}
400496
}

compiler/rustc_attr_parsing/src/diagnostics.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,22 @@ pub(crate) struct DocAutoCfgExpectsHideOrShow;
169169
pub(crate) struct AmbiguousDeriveHelpers;
170170

171171
#[derive(Diagnostic)]
172-
#[diag("`#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items")]
172+
#[diag("`#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or `values(...)`")]
173173
pub(crate) struct DocAutoCfgHideShowUnexpectedItem {
174174
pub attr_name: Symbol,
175175
}
176176

177+
#[derive(Diagnostic)]
178+
#[diag("`any()` was used when other values were provided")]
179+
pub(crate) struct DocAutoCfgHideShowValuesMix {
180+
#[label("value declared here")]
181+
pub value_span: Span,
182+
}
183+
184+
#[derive(Diagnostic)]
185+
#[diag("unexpected item after `values()`")]
186+
pub(crate) struct DocAutoCfgHideShowUnexpectedItemAfterValues;
187+
177188
#[derive(Diagnostic)]
178189
#[diag("`#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items")]
179190
pub(crate) struct DocAutoCfgHideShowExpectsList {
@@ -239,6 +250,10 @@ pub(crate) struct DocUnknownAny {
239250
#[diag("expected boolean for `#[doc(auto_cfg = ...)]`")]
240251
pub(crate) struct DocAutoCfgWrongLiteral;
241252

253+
#[derive(Diagnostic)]
254+
#[diag("there must be at least one identifier before `values(...)`")]
255+
pub(crate) struct DocAutoCfgHideShowNoIdentBeforeValues;
256+
242257
#[derive(Diagnostic)]
243258
#[diag("`#[doc(test(...)]` takes a list of attributes")]
244259
pub(crate) struct DocTestTakesList;

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -515,27 +515,74 @@ pub enum HideOrShow {
515515
Show,
516516
}
517517

518-
#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
519-
pub struct CfgInfo {
520-
pub name: Symbol,
521-
pub name_span: Span,
522-
pub value: Option<(Symbol, Span)>,
518+
#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute, PartialEq)]
519+
pub struct DocCfgHideShowValue {
520+
pub span: Span,
521+
/// If `value` is `None`, then it's a `none()` value.
522+
pub value: Option<Symbol>,
523+
}
524+
525+
impl DocCfgHideShowValue {
526+
pub fn new(value: Symbol, span: Span) -> Self {
527+
Self { span, value: Some(value) }
528+
}
529+
530+
pub fn new_none(span: Span) -> Self {
531+
Self { span, value: None }
532+
}
533+
}
534+
535+
#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute, PartialEq)]
536+
pub enum DocCfgHideShow {
537+
Any(Span),
538+
List(ThinVec<DocCfgHideShowValue>),
523539
}
524540

525-
impl CfgInfo {
526-
pub fn span_for_name_and_value(&self) -> Span {
527-
if let Some((_, value_span)) = self.value {
528-
self.name_span.with_hi(value_span.hi())
529-
} else {
530-
self.name_span
541+
impl DocCfgHideShow {
542+
pub fn new() -> Self {
543+
Self::List(ThinVec::new())
544+
}
545+
546+
pub fn new_with_only_key(span: Span) -> Self {
547+
let mut values = ThinVec::with_capacity(1);
548+
values.push(DocCfgHideShowValue { span, value: None });
549+
Self::List(values)
550+
}
551+
552+
pub fn push_none(&mut self, span: Span) {
553+
if let Self::List(values) = self
554+
&& !values.iter().any(|v| v.value.is_none())
555+
{
556+
values.push(DocCfgHideShowValue { span, value: None });
557+
}
558+
}
559+
560+
pub fn merge_with(&mut self, other: &Self) {
561+
match (self, other) {
562+
(Self::Any(_), Self::Any(_) | Self::List(_)) => {
563+
// Nothing to do.
564+
}
565+
(s, Self::Any(span)) => {
566+
// We "upgrade" the list values to "all".
567+
*s = Self::Any(*span);
568+
}
569+
(Self::List(values), Self::List(other_values)) => {
570+
// Having duplicates is not an issue, we simply ignore them. Would be more
571+
// convenient to have a `set` type though. T_T
572+
for other in other_values {
573+
if !values.iter().any(|value| value.value == other.value) {
574+
values.push(*other);
575+
}
576+
}
577+
}
531578
}
532579
}
533580
}
534581

535582
#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
536583
pub struct CfgHideShow {
537584
pub kind: HideOrShow,
538-
pub values: ThinVec<CfgInfo>,
585+
pub values: FxIndexMap<Symbol, DocCfgHideShow>,
539586
}
540587

541588
#[derive(Clone, Debug, Default, StableHash, Decodable, PrintAttribute)]

compiler/rustc_hir/src/attrs/pretty_printing.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ impl<T: PrintAttribute> PrintAttribute for ThinVec<T> {
8181
p.word("]");
8282
}
8383
}
84-
impl<T: PrintAttribute> PrintAttribute for FxIndexMap<T, Span> {
84+
impl<T: PrintAttribute, T2: PrintAttribute> PrintAttribute for FxIndexMap<T, T2> {
8585
fn should_render(&self) -> bool {
8686
self.is_empty() || self[0].should_render()
8787
}

library/alloc/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@
6565
issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
6666
test(no_crate_inject, attr(allow(unused_variables, duplicate_features), deny(warnings)))
6767
)]
68-
#![doc(auto_cfg(hide(no_global_oom_handling, no_rc, no_sync, target_has_atomic = "ptr")))]
68+
#![doc(auto_cfg(
69+
hide(no_global_oom_handling, no_rc, no_sync),
70+
hide(target_has_atomic, values("ptr")),
71+
))]
6972
#![doc(rust_logo)]
7073
#![feature(rustdoc_internals)]
7174
#![no_std]

0 commit comments

Comments
 (0)