Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
170 changes: 133 additions & 37 deletions compiler/rustc_attr_parsing/src/attributes/doc.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};
use rustc_errors::{Applicability, msg};
use rustc_feature::AttributeStability;
use rustc_hir::Target;
use rustc_hir::attrs::{
AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow,
AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue,
DocInline, HideOrShow,
};
use rustc_session::errors::feature_err;
use rustc_span::{Span, Symbol, edition, sym};
use thin_vec::ThinVec;

use super::prelude::{ALL_TARGETS, AllowedTargets};
use super::{AcceptMapping, AttributeParser, template};
use crate::context::{AcceptContext, FinalizeContext};
use crate::diagnostics::{
AttrCrateLevelOnly, DocAliasDuplicated, DocAutoCfgExpectsHideOrShow,
DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowUnexpectedItem, DocAutoCfgWrongLiteral,
DocTestLiteral, DocTestTakesList, DocTestUnknown, DocUnknownAny, DocUnknownInclude,
DocUnknownPasses, DocUnknownPlugins, DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs,
IllFormedAttributeInput, MalformedDoc,
DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues,
DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues,
DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocTestLiteral, DocTestTakesList,
DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins,
DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput, MalformedDoc,
};
use crate::parser::{
ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser,
};
use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser};
use crate::session_diagnostics::{
DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttrNotCrateLevel,
DocAttributeNotAttribute, DocKeywordNotKeyword, UnusedDuplicate,
Expand Down Expand Up @@ -304,6 +308,81 @@ impl DocParser {
}
}

// Parses the `doc(auto_cfg(hide/show(..., values())))` attribute.
fn parse_auto_cfg_values(
&self,
cx: &mut AcceptContext<'_, '_>,
list: &MetaItemListParser,
values: &mut Option<DocCfgHideShow>,
) {
let mut cfg_values = DocCfgHideShow::new();

let mut values_set = FxHashSet::default();
for item in list.mixed() {
match item {
// If it's a string literal, all good.
MetaItemOrLitParser::Lit(MetaItemLit {
kind: LitKind::Str(symbol, _),
span,
..
}) => match &mut cfg_values {
DocCfgHideShow::Any(any_span) => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
DocAutoCfgHideShowValuesMix { value_span: *span },
*any_span,
);
}
DocCfgHideShow::List(symbols) => {
if values_set.insert(symbol) {
symbols.push(DocCfgHideShowValue::new(*symbol, *span));
}
}
},
// If it's any other kind of literal, then it's wrong and we emit a lint.
MetaItemOrLitParser::Lit(lit) => cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
DocAutoCfgHideShowUnexpectedItem { attr_name: lit.symbol },
lit.span,
),
// If it's a list, then only `any()` and `none()` are allowed and they must not
// contain any item.
MetaItemOrLitParser::MetaItemParser(sub_item) => {
if let Some(ident) = sub_item.ident()
&& [sym::any, sym::none].contains(&ident.name)
&& let ArgParser::List(list) = sub_item.args()
&& list.mixed().count() == 0
{
if ident.name == sym::any {
if let DocCfgHideShow::List(values) = &cfg_values
&& let Some(value) = values.first()
{
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
DocAutoCfgHideShowValuesMix { value_span: value.span },
sub_item.span(),
);
} else {
cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span()));
}
} else {
cfg_values.push_none(sub_item.span());
}
} else {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
DocAutoCfgHideShowUnexpectedItem {
attr_name: sub_item.ident().unwrap().name,
},
sub_item.span(),
);
}
}
}
}
*values = Some(cfg_values);
}

fn parse_auto_cfg(
&mut self,
cx: &mut AcceptContext<'_, '_>,
Expand All @@ -315,7 +394,7 @@ impl DocParser {
self.attribute.auto_cfg_change.push((true, path.span()));
}
ArgParser::List(list) => {
for meta in list.mixed() {
'main: for meta in list.mixed() {
let MetaItemOrLitParser::MetaItemParser(item) = meta else {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
Expand All @@ -324,6 +403,8 @@ impl DocParser {
);
continue;
};
// Only `hide` and `show` are allowed in `auto_cfg` if it's a list, and both
// must be a list.
let (kind, attr_name) = match item.path().word_sym() {
Some(sym::hide) => (HideOrShow::Hide, sym::hide),
Some(sym::show) => (HideOrShow::Show, sym::show),
Expand All @@ -345,56 +426,71 @@ impl DocParser {
continue;
};

let mut cfg_hide_show = CfgHideShow { kind, values: ThinVec::new() };
let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() };

let mut cfg_names = FxHashSet::default();
let mut values = None;
for item in list.mixed() {
let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
DocAutoCfgHideShowUnexpectedItem { attr_name },
item.span(),
);
continue;
continue 'main;
};
match sub_item.args() {
a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
ArgParser::NoArgs if values.is_none() => {
let Some(name) = sub_item.path().word_sym() else {
// FIXME: remove this method once merged and uncomment the line
// below instead.
// cx.expected_identifier(sub_item.path().span());
cx.adcx().expected_identifier(sub_item.path().span());
continue 'main;
};
cfg_names.insert(name);
}
// The only accepted list is `values()`.
ArgParser::List(list) if values.is_none() => {
let Some(sym::values) = sub_item.path().word_sym() else {
cx.adcx().expected_identifier(sub_item.path().span());
continue 'main;
};
if cfg_names.is_empty() {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
MalformedDoc,
sub_item.path().span(),
);
continue;
};
if let Ok(CfgEntry::NameValue { name, value, .. }) =
super::cfg::parse_name_value(
name,
sub_item.path().span(),
a.as_name_value(),
DocAutoCfgHideShowNoIdentBeforeValues,
sub_item.span(),
cx,
)
{
cfg_hide_show.values.push(CfgInfo {
name,
name_span: sub_item.path().span(),
// If `value` is `Some`, `a.name_value()` will always return
// `Some` as well.
value: value
.map(|v| (v, a.as_name_value().unwrap().value_span)),
})
);
continue 'main;
}
self.parse_auto_cfg_values(cx, list, &mut values);
}
_ => {
// No `name = value` is allowed.
ArgParser::NameValue(_) => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
DocAutoCfgHideShowUnexpectedItem { attr_name },
sub_item.span(),
);
continue;
}
// If `values()` was already used, no item should come after it.
_ => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
DocAutoCfgHideShowUnexpectedItemAfterValues,
sub_item.span(),
);
}
}
}

let values = values.unwrap_or(DocCfgHideShow::new_with_only_key(item.span()));
#[allow(rustc::potential_query_instability)]
for cfg_name in &cfg_names {
match cfg_hide_show.values.entry(*cfg_name) {
IndexEntry::Vacant(v) => {
v.insert(values.clone());
}
IndexEntry::Occupied(mut o) => {
o.get_mut().merge_with(&values);
}
}
}
Expand Down
17 changes: 16 additions & 1 deletion compiler/rustc_attr_parsing/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,22 @@ pub(crate) struct DocAutoCfgExpectsHideOrShow;
pub(crate) struct AmbiguousDeriveHelpers;

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

#[derive(Diagnostic)]
#[diag("`any()` was used when other values were provided")]
pub(crate) struct DocAutoCfgHideShowValuesMix {
#[label("value declared here")]
pub value_span: Span,
}

#[derive(Diagnostic)]
#[diag("unexpected item after `values()`")]
pub(crate) struct DocAutoCfgHideShowUnexpectedItemAfterValues;

#[derive(Diagnostic)]
#[diag("`#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items")]
pub(crate) struct DocAutoCfgHideShowExpectsList {
Expand Down Expand Up @@ -239,6 +250,10 @@ pub(crate) struct DocUnknownAny {
#[diag("expected boolean for `#[doc(auto_cfg = ...)]`")]
pub(crate) struct DocAutoCfgWrongLiteral;

#[derive(Diagnostic)]
#[diag("there must be at least one identifier before `values(...)`")]
pub(crate) struct DocAutoCfgHideShowNoIdentBeforeValues;

#[derive(Diagnostic)]
#[diag("`#[doc(test(...)]` takes a list of attributes")]
pub(crate) struct DocTestTakesList;
Expand Down
71 changes: 59 additions & 12 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,27 +515,74 @@ pub enum HideOrShow {
Show,
}

#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
pub struct CfgInfo {
pub name: Symbol,
pub name_span: Span,
pub value: Option<(Symbol, Span)>,
#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute, PartialEq)]
pub struct DocCfgHideShowValue {
pub span: Span,
/// If `value` is `None`, then it's a `none()` value.
pub value: Option<Symbol>,
}

impl DocCfgHideShowValue {
pub fn new(value: Symbol, span: Span) -> Self {
Self { span, value: Some(value) }
}

pub fn new_none(span: Span) -> Self {
Self { span, value: None }
}
}

#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute, PartialEq)]
pub enum DocCfgHideShow {
Any(Span),
List(ThinVec<DocCfgHideShowValue>),
}

impl CfgInfo {
pub fn span_for_name_and_value(&self) -> Span {
if let Some((_, value_span)) = self.value {
self.name_span.with_hi(value_span.hi())
} else {
self.name_span
impl DocCfgHideShow {
pub fn new() -> Self {
Self::List(ThinVec::new())
}

pub fn new_with_only_key(span: Span) -> Self {
let mut values = ThinVec::with_capacity(1);
values.push(DocCfgHideShowValue { span, value: None });
Self::List(values)
}

pub fn push_none(&mut self, span: Span) {
if let Self::List(values) = self
&& !values.iter().any(|v| v.value.is_none())
{
values.push(DocCfgHideShowValue { span, value: None });
}
}

pub fn merge_with(&mut self, other: &Self) {
match (self, other) {
(Self::Any(_), Self::Any(_) | Self::List(_)) => {
// Nothing to do.
}
(s, Self::Any(span)) => {
// We "upgrade" the list values to "all".
*s = Self::Any(*span);
}
(Self::List(values), Self::List(other_values)) => {
// Having duplicates is not an issue, we simply ignore them. Would be more
// convenient to have a `set` type though. T_T
for other in other_values {
if !values.iter().any(|value| value.value == other.value) {
values.push(*other);
}
}
}
}
}
}

#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
pub struct CfgHideShow {
pub kind: HideOrShow,
pub values: ThinVec<CfgInfo>,
pub values: FxIndexMap<Symbol, DocCfgHideShow>,
}

#[derive(Clone, Debug, Default, StableHash, Decodable, PrintAttribute)]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir/src/attrs/pretty_printing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl<T: PrintAttribute> PrintAttribute for ThinVec<T> {
p.word("]");
}
}
impl<T: PrintAttribute> PrintAttribute for FxIndexMap<T, Span> {
impl<T: PrintAttribute, T2: PrintAttribute> PrintAttribute for FxIndexMap<T, T2> {
fn should_render(&self) -> bool {
self.is_empty() || self[0].should_render()
}
Expand Down
5 changes: 4 additions & 1 deletion library/alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@
issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
test(no_crate_inject, attr(allow(unused_variables, duplicate_features), deny(warnings)))
)]
#![doc(auto_cfg(hide(no_global_oom_handling, no_rc, no_sync, target_has_atomic = "ptr")))]
#![doc(auto_cfg(
hide(no_global_oom_handling, no_rc, no_sync),
hide(target_has_atomic, values("ptr")),
))]
#![doc(rust_logo)]
#![feature(rustdoc_internals)]
#![no_std]
Expand Down
Loading
Loading