diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 7cb1fede1741a..e38e7ee52393f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -4,14 +4,29 @@ use rustc_attr_data_structures::RustcVersion; use rustc_feature::{Features, GatedCfg, find_gated_cfg}; use rustc_session::Session; use rustc_session::config::ExpectedValues; -use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::UNEXPECTED_CFGS; +use rustc_session::lint::{BuiltinLintDiag, Lint}; use rustc_session::parse::feature_err; use rustc_span::{Span, Symbol, sym}; use crate::session_diagnostics::{self, UnsupportedLiteralReason}; use crate::{fluent_generated, parse_version}; +/// Emitter of a builtin lint from `cfg_matches`. +/// +/// Used to support emiting a lint (currently on check-cfg), either: +/// - as an early buffered lint (in `rustc`) +/// - or has a "normal" lint from HIR (in `rustdoc`) +pub trait CfgMatchesLintEmitter { + fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag); +} + +impl CfgMatchesLintEmitter for NodeId { + fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag) { + sess.psess.buffer_lint(lint, sp, *self, diag); + } +} + #[derive(Clone, Debug)] pub struct Condition { pub name: Symbol, @@ -25,17 +40,17 @@ pub struct Condition { pub fn cfg_matches( cfg: &MetaItemInner, sess: &Session, - lint_node_id: NodeId, + lint_emitter: impl CfgMatchesLintEmitter, features: Option<&Features>, ) -> bool { eval_condition(cfg, sess, features, &mut |cfg| { try_gate_cfg(cfg.name, cfg.span, sess, features); match sess.psess.check_config.expecteds.get(&cfg.name) { Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => { - sess.psess.buffer_lint( + lint_emitter.emit_span_lint( + sess, UNEXPECTED_CFGS, cfg.span, - lint_node_id, BuiltinLintDiag::UnexpectedCfgValue( (cfg.name, cfg.name_span), cfg.value.map(|v| (v, cfg.value_span.unwrap())), @@ -43,10 +58,10 @@ pub fn cfg_matches( ); } None if sess.psess.check_config.exhaustive_names => { - sess.psess.buffer_lint( + lint_emitter.emit_span_lint( + sess, UNEXPECTED_CFGS, cfg.span, - lint_node_id, BuiltinLintDiag::UnexpectedCfgName( (cfg.name, cfg.name_span), cfg.value.map(|v| (v, cfg.value_span.unwrap())), diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index f9601fa5ef1d8..2607ec58f4d93 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -18,7 +18,7 @@ use tracing::debug; use crate::context::{EarlyContext, LintContext, LintStore}; use crate::passes::{EarlyLintPass, EarlyLintPassObject}; -mod diagnostics; +pub(super) mod diagnostics; macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({ $cx.pass.$f(&$cx.context, $($args),*); @@ -40,7 +40,7 @@ impl<'ecx, 'tcx, T: EarlyLintPass> EarlyContextAndPass<'ecx, 'tcx, T> { for early_lint in self.context.buffered.take(id) { let BufferedEarlyLint { span, node_id: _, lint_id, diagnostic } = early_lint; self.context.opt_span_lint(lint_id.lint, span, |diag| { - diagnostics::decorate_lint(self.context.sess(), self.tcx, diagnostic, diag); + diagnostics::decorate_builtin_lint(self.context.sess(), self.tcx, diagnostic, diag); }); } } diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 40ca9e05d95d6..8987b286cf72f 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -18,7 +18,7 @@ use crate::lints::{self, ElidedNamedLifetime}; mod check_cfg; -pub(super) fn decorate_lint( +pub fn decorate_builtin_lint( sess: &Session, tcx: Option>, diagnostic: BuiltinLintDiag, diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index b910d6a138e11..af776ef5184d0 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -127,6 +127,7 @@ use unused::*; #[rustfmt::skip] pub use builtin::{MissingDoc, SoftLints}; pub use context::{CheckLintNameResult, EarlyContext, LateContext, LintContext, LintStore}; +pub use early::diagnostics::decorate_builtin_lint; pub use early::{EarlyCheckNode, check_ast_node}; pub use late::{check_crate, late_lint_mod, unerased_lint_store}; pub use levels::LintLevelsBuilder; diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 55a116a018a8a..f25cf60681297 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -409,12 +409,12 @@ pub(crate) fn merge_attrs( } else { Attributes::from_hir(&both) }, - extract_cfg_from_attrs(both.iter(), cx.tcx, &cx.cache.hidden_cfg), + extract_cfg_from_attrs(both.iter(), cx.tcx, None, &cx.cache.hidden_cfg), ) } else { ( Attributes::from_hir(old_attrs), - extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg), + extract_cfg_from_attrs(old_attrs.iter(), cx.tcx, None, &cx.cache.hidden_cfg), ) } } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 622a410837b54..e0dce4e2a8518 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -210,6 +210,7 @@ fn generate_item_with_correct_attrs( Cow::Owned(attr) => attr, }), cx.tcx, + def_id.as_local().map(|did| cx.tcx.local_def_id_to_hir_id(did)), &cx.cache.hidden_cfg, ); let attrs = Attributes::from_hir_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false); diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index bbe11bf56af30..35871c1c2fae4 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -10,8 +10,9 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::lang_items::LangItem; -use rustc_hir::{BodyId, Mutability}; +use rustc_hir::{BodyId, HirId, Mutability}; use rustc_index::IndexVec; +use rustc_lint_defs::{BuiltinLintDiag, Lint}; use rustc_metadata::rendered_const; use rustc_middle::span_bug; use rustc_middle::ty::fast_reject::SimplifiedType; @@ -475,7 +476,12 @@ impl Item { name, kind, Attributes::from_hir(hir_attrs), - extract_cfg_from_attrs(hir_attrs.iter(), cx.tcx, &cx.cache.hidden_cfg), + extract_cfg_from_attrs( + hir_attrs.iter(), + cx.tcx, + def_id.as_local().map(|did| cx.tcx.local_def_id_to_hir_id(did)), + &cx.cache.hidden_cfg, + ), ) } @@ -1014,6 +1020,7 @@ pub(crate) fn hir_attr_lists<'a, I: IntoIterator>( pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator + Clone>( attrs: I, tcx: TyCtxt<'_>, + hir_id: Option, hidden_cfg: &FxHashSet, ) -> Option> { let doc_cfg_active = tcx.features().doc_cfg(); @@ -1037,6 +1044,32 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator .peekable(); if doc_cfg.peek().is_some() && doc_cfg_active { let sess = tcx.sess; + + struct RustdocCfgMatchesLintEmitter<'a>(TyCtxt<'a>, Option); + + impl<'a> rustc_attr_parsing::CfgMatchesLintEmitter for RustdocCfgMatchesLintEmitter<'a> { + fn emit_span_lint( + &self, + sess: &Session, + lint: &'static Lint, + sp: rustc_span::Span, + builtin_diag: BuiltinLintDiag, + ) { + if let Some(hir_id) = self.1 { + self.0.node_span_lint(lint, hir_id, sp, |diag| { + rustc_lint::decorate_builtin_lint( + sess, + Some(self.0), + builtin_diag, + diag, + ) + }); + } else { + // no HIR id, probably in another crate don't lint + } + } + } + doc_cfg.fold(Cfg::True, |mut cfg, item| { if let Some(cfg_mi) = item.meta_item().and_then(|item| rustc_expand::config::parse_cfg(item, sess)) @@ -1045,7 +1078,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator rustc_attr_parsing::cfg_matches( cfg_mi, tcx.sess, - rustc_ast::CRATE_NODE_ID, + RustdocCfgMatchesLintEmitter(tcx, hir_id), Some(tcx.features()), ); match Cfg::parse(cfg_mi) { diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index 43dcfab880b5a..0f61be59c7692 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -95,9 +95,12 @@ impl HirCollector<'_> { nested: F, ) { let ast_attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id)); - if let Some(ref cfg) = - extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &FxHashSet::default()) - && !cfg.matches(&self.tcx.sess.psess) + if let Some(ref cfg) = extract_cfg_from_attrs( + ast_attrs.iter(), + self.tcx, + Some(self.tcx.local_def_id_to_hir_id(def_id)), + &FxHashSet::default(), + ) && !cfg.matches(&self.tcx.sess.psess) { return; } diff --git a/tests/rustdoc-ui/doc-cfg-check-cfg.cfg_empty.stderr b/tests/rustdoc-ui/doc-cfg-check-cfg.cfg_empty.stderr new file mode 100644 index 0000000000000..7e6f8dec5ed06 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-check-cfg.cfg_empty.stderr @@ -0,0 +1,12 @@ +warning: unexpected `cfg` condition name: `foo` + --> $DIR/doc-cfg-check-cfg.rs:13:11 + | +LL | #[doc(cfg(foo))] + | ^^^ + | + = help: to expect this configuration use `--check-cfg=cfg(foo)` + = note: see for more information about checking conditional configuration + = note: `#[warn(unexpected_cfgs)]` on by default + +warning: 1 warning emitted + diff --git a/tests/rustdoc-ui/doc-cfg-check-cfg.rs b/tests/rustdoc-ui/doc-cfg-check-cfg.rs index e3420dc078978..6bb520b0726d0 100644 --- a/tests/rustdoc-ui/doc-cfg-check-cfg.rs +++ b/tests/rustdoc-ui/doc-cfg-check-cfg.rs @@ -1,16 +1,21 @@ // Ensure that `doc(cfg())` respects `check-cfg` // Currently not properly working -#![feature(doc_cfg)] -#![deny(unexpected_cfgs)] -//@revisions: no_check cfg_empty cfg_foo +//@ check-pass +//@ no-auto-check-cfg + +//@ revisions: no_check cfg_empty cfg_foo //@[cfg_empty] compile-flags: --check-cfg cfg() //@[cfg_foo] compile-flags: --check-cfg cfg(foo) -//@[no_check] check-pass -//@[cfg_empty] check-pass -//@[cfg_empty] known-bug: #138358 -//@[cfg_foo] check-pass +#![feature(doc_cfg)] #[doc(cfg(foo))] +//[cfg_empty]~^ WARN unexpected `cfg` condition name: `foo` pub fn foo() {} + +pub mod module { + #[allow(unexpected_cfgs)] + #[doc(cfg(bar))] + pub fn bar() {} +} diff --git a/tests/rustdoc-ui/doc-cfg.rs b/tests/rustdoc-ui/doc-cfg.rs index 354d76bc3c433..14943bbc3418e 100644 --- a/tests/rustdoc-ui/doc-cfg.rs +++ b/tests/rustdoc-ui/doc-cfg.rs @@ -3,7 +3,9 @@ #[doc(cfg(), cfg(foo, bar))] //~^ ERROR //~^^ ERROR -#[doc(cfg(foo), cfg(bar))] // ok! +#[doc(cfg(foo), cfg(bar))] +//~^ WARN unexpected `cfg` condition name: `foo` +//~^^ WARN unexpected `cfg` condition name: `bar` #[doc(cfg())] //~ ERROR #[doc(cfg(foo, bar))] //~ ERROR pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-cfg.stderr b/tests/rustdoc-ui/doc-cfg.stderr index 14b7b17e04d3a..48c8e79ce9625 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -10,17 +10,37 @@ error: multiple `cfg` predicates are specified LL | #[doc(cfg(), cfg(foo, bar))] | ^^^ +warning: unexpected `cfg` condition name: `foo` + --> $DIR/doc-cfg.rs:6:11 + | +LL | #[doc(cfg(foo), cfg(bar))] + | ^^^ + | + = help: expected names are: `FALSE` and `test` and 31 more + = help: to expect this configuration use `--check-cfg=cfg(foo)` + = note: see for more information about checking conditional configuration + = note: `#[warn(unexpected_cfgs)]` on by default + +warning: unexpected `cfg` condition name: `bar` + --> $DIR/doc-cfg.rs:6:21 + | +LL | #[doc(cfg(foo), cfg(bar))] + | ^^^ + | + = help: to expect this configuration use `--check-cfg=cfg(bar)` + = note: see for more information about checking conditional configuration + error: `cfg` predicate is not specified - --> $DIR/doc-cfg.rs:7:7 + --> $DIR/doc-cfg.rs:9:7 | LL | #[doc(cfg())] | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` error: multiple `cfg` predicates are specified - --> $DIR/doc-cfg.rs:8:16 + --> $DIR/doc-cfg.rs:10:16 | LL | #[doc(cfg(foo, bar))] | ^^^ -error: aborting due to 4 previous errors +error: aborting due to 4 previous errors; 2 warnings emitted