Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto_cfg and no_auto_cfg attributes #102599

Closed
wants to merge 9 commits into from
9 changes: 9 additions & 0 deletions compiler/rustc_error_messages/locales/en-US/passes.ftl
Expand Up @@ -75,6 +75,15 @@ passes_doc_alias_not_string_literal = `#[doc(alias("a"))]` expects string litera
passes_doc_alias_malformed =
doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]`

passes_doc_auto_cfg_malformed = `#![doc({$attr_str})]` attribute doesn't expect a value

passes_doc_both_auto_cfg = conflicting attributes `doc(auto_cfg)` and `doc(no_auto_cfg)`
.label = the other is specified here

passes_doc_no_auto_cfg_enabled_by_default = `doc(no_auto_cfg)` is enabled by default before the 2024 edition

passes_doc_auto_cfg_enabled_by_default = `doc(auto_cfg)` is enabled by default since the 2024 edition

passes_doc_keyword_empty_mod = `#[doc(keyword = "...")]` should be used on empty modules

passes_doc_keyword_not_mod = `#[doc(keyword = "...")]` should be used on modules
Expand Down
69 changes: 68 additions & 1 deletion compiler/rustc_passes/src/check_attr.rs
Expand Up @@ -5,7 +5,7 @@
//! item.

use crate::errors;
use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{fluent, struct_span_err, Applicability, MultiSpan};
use rustc_expand::base::resolve_path;
Expand All @@ -25,6 +25,7 @@ use rustc_session::lint::builtin::{
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, UNUSED_ATTRIBUTES,
};
use rustc_session::parse::feature_err;
use rustc_span::edition::Edition;
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::{Span, DUMMY_SP};
use rustc_target::spec::abi::Abi;
Expand Down Expand Up @@ -97,6 +98,7 @@ impl CheckAttrVisitor<'_> {
target,
&mut specified_inline,
&mut doc_aliases,
&mut seen,
),
sym::no_link => self.check_no_link(hir_id, &attr, span, target),
sym::export_name => self.check_export_name(hir_id, &attr, span, target),
Expand Down Expand Up @@ -945,6 +947,61 @@ impl CheckAttrVisitor<'_> {
is_valid
}

/// Checks that `doc(auto_cfg)` is valid (i.e. no value) and warn if it's used whereas the
/// "equivalent feature" is already enabled.
fn check_auto_cfg(
&self,
meta: &MetaItem,
hir_id: HirId,
seen: &mut FxHashMap<Symbol, Span>,
) -> bool {
let name = meta.name_or_empty();
if !meta.is_word() {
self.tcx
.sess
.emit_err(errors::DocAutoCfgMalformed { span: meta.span, attr_str: name.as_str() });
return false;
}
let mut is_valid = true;
let other = if name == sym::no_auto_cfg {
if self.tcx.sess.edition() < Edition::Edition2024 {
self.tcx.emit_spanned_lint(
UNUSED_ATTRIBUTES,
hir_id,
meta.span,
errors::DocNoAutoCfgEnabledByDefault,
);
is_valid = false;
}
sym::auto_cfg
} else {
if self.tcx.sess.edition() > Edition::Edition2021 {
self.tcx.emit_spanned_lint(
UNUSED_ATTRIBUTES,
hir_id,
meta.span,
errors::DocAutoCfgEnabledByDefault,
);
is_valid = false;
}
sym::no_auto_cfg
};

match seen.entry(other) {
Entry::Occupied(entry) => {
self.tcx
.sess
.emit_err(errors::BothDocAutoCfg { span: *entry.get(), attr_span: meta.span });
is_valid = false;
}
Entry::Vacant(entry) => {
entry.insert(meta.span);
}
}

is_valid
}

/// Runs various checks on `#[doc]` attributes. Returns `true` if valid.
///
/// `specified_inline` should be initialized to `None` and kept for the scope
Expand All @@ -958,6 +1015,7 @@ impl CheckAttrVisitor<'_> {
target: Target,
specified_inline: &mut Option<(bool, Span)>,
aliases: &mut FxHashMap<String, Span>,
seen: &mut FxHashMap<Symbol, Span>,
) -> bool {
let mut is_valid = true;

Expand Down Expand Up @@ -993,6 +1051,8 @@ impl CheckAttrVisitor<'_> {
| sym::html_root_url
| sym::html_no_source
| sym::test
| sym::auto_cfg
| sym::no_auto_cfg
if !self.check_attr_crate_level(attr, meta, hir_id) =>
{
is_valid = false;
Expand All @@ -1010,6 +1070,11 @@ impl CheckAttrVisitor<'_> {
is_valid = false;
}

sym::auto_cfg | sym::no_auto_cfg
if !self.check_auto_cfg(i_meta, hir_id, seen) => {
is_valid = false;
}

// no_default_passes: deprecated
// passes: deprecated
// plugins: removed, but rustdoc warns about it itself
Expand All @@ -1031,6 +1096,8 @@ impl CheckAttrVisitor<'_> {
| sym::notable_trait
| sym::passes
| sym::plugins
| sym::auto_cfg
| sym::no_auto_cfg
| sym::fake_variadic => {}

sym::test => {
Expand Down
25 changes: 25 additions & 0 deletions compiler/rustc_passes/src/errors.rs
Expand Up @@ -189,6 +189,31 @@ pub struct DocAliasMalformed {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(passes::doc_auto_cfg_malformed)]
pub struct DocAutoCfgMalformed<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}

#[derive(Diagnostic)]
#[diag(passes::doc_both_auto_cfg)]
pub struct BothDocAutoCfg {
#[primary_span]
pub span: Span,
#[label]
pub attr_span: Span,
}

#[derive(LintDiagnostic)]
#[diag(passes::doc_auto_cfg_enabled_by_default)]
pub struct DocAutoCfgEnabledByDefault;

#[derive(LintDiagnostic)]
#[diag(passes::doc_no_auto_cfg_enabled_by_default)]
pub struct DocNoAutoCfgEnabledByDefault;

#[derive(Diagnostic)]
#[diag(passes::doc_keyword_empty_mod)]
pub struct DocKeywordEmptyMod {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Expand Up @@ -408,6 +408,7 @@ symbols! {
attr_literals,
attributes,
augmented_assignments,
auto_cfg,
auto_traits,
automatically_derived,
avx,
Expand Down Expand Up @@ -1009,6 +1010,7 @@ symbols! {
next,
nll,
no,
no_auto_cfg,
no_builtins,
no_core,
no_coverage,
Expand Down
1 change: 1 addition & 0 deletions src/doc/rustdoc/src/unstable-features.md
Expand Up @@ -96,6 +96,7 @@ previous source code:

```rust
#![feature(doc_auto_cfg)]
#![doc(auto_cfg)] // To enable the "auto_cfg" discovery.

/// Token struct that can only be used on Windows.
#[cfg(any(windows, doc))]
Expand Down
7 changes: 5 additions & 2 deletions src/librustdoc/clean/inline.rs
Expand Up @@ -318,10 +318,13 @@ pub(crate) fn merge_attrs(
} else {
Attributes::from_ast(&both)
},
both.cfg(cx.tcx, &cx.cache.hidden_cfg),
both.cfg(cx.tcx, &cx.cache.hidden_cfg, cx.cache.doc_auto_cfg_active),
)
} else {
(Attributes::from_ast(&old_attrs), old_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg))
(
Attributes::from_ast(&old_attrs),
old_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg, cx.cache.doc_auto_cfg_active),
)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/clean/mod.rs
Expand Up @@ -2112,7 +2112,7 @@ fn clean_extern_crate<'tcx>(
item_id: crate_def_id.into(),
visibility: clean_visibility(ty_vis),
kind: Box::new(ExternCrateItem { src: orig_name }),
cfg: attrs.cfg(cx.tcx, &cx.cache.hidden_cfg),
cfg: attrs.cfg(cx.tcx, &cx.cache.hidden_cfg, cx.cache.doc_auto_cfg_active),
}]
}

Expand Down
17 changes: 13 additions & 4 deletions src/librustdoc/clean/types.rs
Expand Up @@ -463,7 +463,7 @@ impl Item {
kind,
Box::new(Attributes::from_ast(ast_attrs)),
cx,
ast_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg),
ast_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg, cx.cache.doc_auto_cfg_active),
)
}

Expand Down Expand Up @@ -838,7 +838,12 @@ pub(crate) trait AttributesExt {

fn inner_docs(&self) -> bool;

fn cfg(&self, tcx: TyCtxt<'_>, hidden_cfg: &FxHashSet<Cfg>) -> Option<Arc<Cfg>>;
fn cfg(
&self,
tcx: TyCtxt<'_>,
hidden_cfg: &FxHashSet<Cfg>,
doc_auto_cfg_active: bool,
) -> Option<Arc<Cfg>>;
}

impl AttributesExt for [ast::Attribute] {
Expand All @@ -864,10 +869,14 @@ impl AttributesExt for [ast::Attribute] {
self.iter().find(|a| a.doc_str().is_some()).map_or(true, |a| a.style == AttrStyle::Inner)
}

fn cfg(&self, tcx: TyCtxt<'_>, hidden_cfg: &FxHashSet<Cfg>) -> Option<Arc<Cfg>> {
fn cfg(
&self,
tcx: TyCtxt<'_>,
hidden_cfg: &FxHashSet<Cfg>,
doc_auto_cfg_active: bool,
) -> Option<Arc<Cfg>> {
let sess = tcx.sess;
let doc_cfg_active = tcx.features().doc_cfg;
let doc_auto_cfg_active = tcx.features().doc_auto_cfg;

fn single<T: IntoIterator>(it: T) -> Option<T::Item> {
let mut iter = it.into_iter();
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/core.rs
Expand Up @@ -245,6 +245,7 @@ pub(crate) fn create_config(
rustc_lint::builtin::UNEXPECTED_CFGS.name.to_string(),
// this lint is needed to support `#[expect]` attributes
rustc_lint::builtin::UNFULFILLED_LINT_EXPECTATIONS.name.to_string(),
rustc_lint::builtin::UNUSED_ATTRIBUTES.name.to_string(),
];
lints_to_show.extend(crate::lint::RUSTDOC_LINTS.iter().map(|lint| lint.name.to_string()));

Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/doctest.rs
Expand Up @@ -1210,7 +1210,7 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
nested: F,
) {
let ast_attrs = self.tcx.hir().attrs(hir_id);
if let Some(ref cfg) = ast_attrs.cfg(self.tcx, &FxHashSet::default()) {
if let Some(ref cfg) = ast_attrs.cfg(self.tcx, &FxHashSet::default(), false) {
if !cfg.matches(&self.sess.parse_sess, Some(self.sess.features_untracked())) {
return;
}
Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/formats/cache.rs
Expand Up @@ -121,6 +121,8 @@ pub(crate) struct Cache {
pub(crate) intra_doc_links: FxHashMap<ItemId, Vec<clean::ItemLink>>,
/// Cfg that have been hidden via #![doc(cfg_hide(...))]
pub(crate) hidden_cfg: FxHashSet<clean::cfg::Cfg>,
/// Whether or not the `#![doc(auto_cfg)]` attribute was used.
pub(crate) doc_auto_cfg_active: bool,
}

/// This struct is used to wrap the `cache` and `tcx` in order to run `DocFolder`.
Expand Down
6 changes: 5 additions & 1 deletion src/librustdoc/html/render/print_item.rs
Expand Up @@ -351,7 +351,11 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
let import_item = clean::Item {
item_id: import_def_id.into(),
attrs: import_attrs,
cfg: ast_attrs.cfg(cx.tcx(), &cx.cache().hidden_cfg),
cfg: ast_attrs.cfg(
cx.tcx(),
&cx.cache().hidden_cfg,
cx.cache().doc_auto_cfg_active,
),
..myitem.clone()
};

Expand Down
28 changes: 28 additions & 0 deletions src/librustdoc/visit_ast.rs
Expand Up @@ -10,6 +10,7 @@ use rustc_hir::CRATE_HIR_ID;
use rustc_middle::middle::privacy::AccessLevel;
use rustc_middle::ty::TyCtxt;
use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
use rustc_span::edition::Edition;
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::Span;

Expand Down Expand Up @@ -146,6 +147,33 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
.into_iter(),
)
.collect();
if self.cx.tcx.features().doc_auto_cfg {
// This feature is enabled by default starting the 2024 edition.
self.cx.cache.doc_auto_cfg_active = self.cx.tcx.sess.edition() > Edition::Edition2021;
if let Some(attr) = self
.cx
.tcx
.hir()
.attrs(CRATE_HIR_ID)
.iter()
.filter(|attr| attr.has_name(sym::doc))
.flat_map(|attr| attr.meta_item_list().into_iter().flatten())
.find(|attr| attr.has_name(sym::auto_cfg) || attr.has_name(sym::no_auto_cfg))
{
// If we find one of the two attributes, we update the default value of
// `doc_auto_cfg_active`.
self.cx.cache.doc_auto_cfg_active = attr.has_name(sym::auto_cfg);
} else {
self.cx
.sess()
.diagnostic()
.struct_warn(
"feature `doc_auto_cfg` now requires attribute `#![doc(auto_cfg)]` to \
enable the behavior",
)
.emit();
}
}

self.cx.cache.exact_paths = self.exact_paths;
top_level_module
Expand Down
2 changes: 2 additions & 0 deletions src/test/rustdoc-ui/doc_auto_cfg.rs
@@ -0,0 +1,2 @@
// check-pass
#![feature(doc_auto_cfg)]
4 changes: 4 additions & 0 deletions src/test/rustdoc-ui/doc_auto_cfg.stderr
@@ -0,0 +1,4 @@
warning: feature `doc_auto_cfg` now requires attribute `#![doc(auto_cfg)]` to enable the behavior

warning: 1 warning emitted

15 changes: 15 additions & 0 deletions src/test/rustdoc-ui/doc_no_auto_cfg.rs
@@ -0,0 +1,15 @@
// edition: 2021
#![deny(warnings)]
#![doc(no_auto_cfg)] //~ ERROR
//~^ ERROR
#![doc(auto_cfg, no_auto_cfg)] //~ ERROR
#![doc(no_auto_cfg(1))] //~ ERROR
#![doc(no_auto_cfg = 1)] //~ ERROR
#![doc(auto_cfg(1))] //~ ERROR
#![doc(auto_cfg = 1)] //~ ERROR

#[doc(auto_cfg)] //~ ERROR
//~^ WARN
#[doc(no_auto_cfg)] //~ ERROR
//~^ WARN
pub struct Bar;