diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index b0dbc2c2340..c0d2e76f310 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -218,6 +218,19 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { } } } + if !attr.is_doc_comment() + && attr.get_normal_item().path.segments.len() == 2 + && attr.get_normal_item().path.segments[0].ident.name == sym::diagnostic + && !self.features.diagnostic_namespace + { + let msg = "`#[diagnostic]` attribute name space is experimental"; + gate_feature_post!( + self, + diagnostic_namespace, + attr.get_normal_item().path.segments[0].ident.span, + msg + ); + } // Emit errors for non-staged-api crates. if !self.features.staged_api { diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index 56a2c5eff3d..c03789f500a 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -379,6 +379,8 @@ declare_features! ( (active, deprecated_safe, "1.61.0", Some(94978), None), /// Allows having using `suggestion` in the `#[deprecated]` attribute. (active, deprecated_suggestion, "1.61.0", Some(94785), None), + /// Allows using the `#[diagnostic]` attribute tool namespace + (active, diagnostic_namespace, "CURRENT_RUSTC_VERSION", Some(94785), None), /// Controls errors in trait implementations. (active, do_not_recommend, "1.67.0", Some(51992), None), /// Tells rustdoc to automatically generate `#[doc(cfg(...))]`. diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index a0f2e9aed81..cfb40a3b65c 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3400,6 +3400,7 @@ declare_lint_pass! { UNFULFILLED_LINT_EXPECTATIONS, UNINHABITED_STATIC, UNKNOWN_CRATE_TYPES, + UNKNOWN_DIAGNOSTIC_ATTRIBUTES, UNKNOWN_LINTS, UNNAMEABLE_TYPES, UNREACHABLE_CODE, @@ -4380,3 +4381,27 @@ declare_lint! { "effective visibility of a type is larger than the area in which it can be named", @feature_gate = sym::type_privacy_lints; } + +declare_lint! { + /// The `unknown_diagnostic_attributes` lint detects unrecognized diagnostic attributes. + /// + /// ### Example + /// + /// ```rust + /// #![feature(diagnostic_namespace)] + /// #[diagnostic::does_not_exist] + /// struct Foo; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// It is usually a mistake to specify a diagnostic attribute that does not exist. Check + /// the spelling, and check the diagnostic attribute listing for the correct name. Also + /// consider if you are using an old version of the compiler, and the attribute + /// is only available in a newer version. + pub UNKNOWN_DIAGNOSTIC_ATTRIBUTES, + Warn, + "unrecognized diagnostic attribute" +} diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 266e37e4cef..d456cc9a9a0 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -24,7 +24,9 @@ use rustc_hir::def_id::{CrateNum, LocalDefId}; use rustc_middle::middle::stability; use rustc_middle::ty::RegisteredTools; use rustc_middle::ty::TyCtxt; -use rustc_session::lint::builtin::{LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE}; +use rustc_session::lint::builtin::{ + LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE, UNKNOWN_DIAGNOSTIC_ATTRIBUTES, +}; use rustc_session::lint::builtin::{UNUSED_MACROS, UNUSED_MACRO_RULES}; use rustc_session::lint::BuiltinLintDiagnostics; use rustc_session::parse::feature_err; @@ -140,9 +142,9 @@ pub(crate) fn registered_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools { } } } - // We implicitly add `rustfmt` and `clippy` to known tools, + // We implicitly add `rustfmt`, `clippy`, `diagnostic` to known tools, // but it's not an error to register them explicitly. - let predefined_tools = [sym::clippy, sym::rustfmt]; + let predefined_tools = [sym::clippy, sym::rustfmt, sym::diagnostic]; registered_tools.extend(predefined_tools.iter().cloned().map(Ident::with_dummy_span)); registered_tools } @@ -595,6 +597,18 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } + if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) + && path.segments.len() >= 2 + && path.segments[0].ident.name == sym::diagnostic + { + self.tcx.sess.parse_sess.buffer_lint( + UNKNOWN_DIAGNOSTIC_ATTRIBUTES, + path.segments[1].span(), + node_id, + "unknown diagnostic attribute", + ); + } + Ok((ext, res)) } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 54eb7bef5f2..1b426ef2048 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -620,6 +620,7 @@ symbols! { destruct, destructuring_assignment, diagnostic, + diagnostic_namespace, direct, discriminant_kind, discriminant_type, diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index f9a90bf3a00..d1ae88fcd80 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -13,7 +13,7 @@ use std::path::{Path, PathBuf}; const ENTRY_LIMIT: usize = 900; // FIXME: The following limits should be reduced eventually. const ISSUES_ENTRY_LIMIT: usize = 1893; -const ROOT_ENTRY_LIMIT: usize = 870; +const ROOT_ENTRY_LIMIT: usize = 871; const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[ "rs", // test source files diff --git a/tests/ui/diagnostic_namespace/auxiliary/proc-macro-helper.rs b/tests/ui/diagnostic_namespace/auxiliary/proc-macro-helper.rs new file mode 100644 index 00000000000..759c32c8453 --- /dev/null +++ b/tests/ui/diagnostic_namespace/auxiliary/proc-macro-helper.rs @@ -0,0 +1,12 @@ +// force-host +// no-prefer-dynamic +#![crate_type = "proc-macro"] + +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn diagnostic(i: TokenStream, _: TokenStream) -> TokenStream { + i +} diff --git a/tests/ui/diagnostic_namespace/can_use_the_diagnostic_name_in_other_places.rs b/tests/ui/diagnostic_namespace/can_use_the_diagnostic_name_in_other_places.rs new file mode 100644 index 00000000000..08b4d68779c --- /dev/null +++ b/tests/ui/diagnostic_namespace/can_use_the_diagnostic_name_in_other_places.rs @@ -0,0 +1,13 @@ +// check-pass + +mod diagnostic {} + +macro_rules! diagnostic{ + () => {} +} + +#[allow(non_upper_case_globals)] +const diagnostic: () = (); + +fn main() { +} diff --git a/tests/ui/diagnostic_namespace/existing_proc_macros.rs b/tests/ui/diagnostic_namespace/existing_proc_macros.rs new file mode 100644 index 00000000000..d6d1fb01496 --- /dev/null +++ b/tests/ui/diagnostic_namespace/existing_proc_macros.rs @@ -0,0 +1,24 @@ +#![feature(diagnostic_namespace)] +// check-pass +// aux-build:proc-macro-helper.rs + +extern crate proc_macro_helper; + +mod test1 { + use proc_macro_helper::diagnostic; + + #[diagnostic] + struct Foo; + +} + +mod test2 { + mod diagnostic { + pub use proc_macro_helper::diagnostic as on_unimplemented; + } + + #[diagnostic::on_unimplemented] + trait Foo {} +} + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.rs b/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.rs new file mode 100644 index 00000000000..a686ed9c84e --- /dev/null +++ b/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.rs @@ -0,0 +1,13 @@ +#[diagnostic::non_existing_attribute] +//~^ERROR `#[diagnostic]` attribute name space is experimental [E0658] +//~|WARNING unknown diagnostic attribute [unknown_diagnostic_attributes] +pub trait Bar { +} + +#[diagnostic::non_existing_attribute(with_option = "foo")] +//~^ERROR `#[diagnostic]` attribute name space is experimental [E0658] +//~|WARNING unknown diagnostic attribute [unknown_diagnostic_attributes] +struct Foo; + +fn main() { +} diff --git a/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.stderr b/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.stderr new file mode 100644 index 00000000000..45c95cbb3c7 --- /dev/null +++ b/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.stderr @@ -0,0 +1,35 @@ +error[E0658]: `#[diagnostic]` attribute name space is experimental + --> $DIR/feature-gate-diagnostic_namespace.rs:1:3 + | +LL | #[diagnostic::non_existing_attribute] + | ^^^^^^^^^^ + | + = note: see issue #94785 for more information + = help: add `#![feature(diagnostic_namespace)]` to the crate attributes to enable + +error[E0658]: `#[diagnostic]` attribute name space is experimental + --> $DIR/feature-gate-diagnostic_namespace.rs:7:3 + | +LL | #[diagnostic::non_existing_attribute(with_option = "foo")] + | ^^^^^^^^^^ + | + = note: see issue #94785 for more information + = help: add `#![feature(diagnostic_namespace)]` to the crate attributes to enable + +warning: unknown diagnostic attribute + --> $DIR/feature-gate-diagnostic_namespace.rs:1:15 + | +LL | #[diagnostic::non_existing_attribute] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unknown_diagnostic_attributes)]` on by default + +warning: unknown diagnostic attribute + --> $DIR/feature-gate-diagnostic_namespace.rs:7:15 + | +LL | #[diagnostic::non_existing_attribute(with_option = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors; 2 warnings emitted + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.rs b/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.rs new file mode 100644 index 00000000000..677bd5a7343 --- /dev/null +++ b/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.rs @@ -0,0 +1,13 @@ +#![feature(diagnostic_namespace)] +// check-pass +#[diagnostic::non_existing_attribute] +//~^WARN unknown diagnostic attribute +pub trait Bar { +} + +#[diagnostic::non_existing_attribute(with_option = "foo")] +//~^WARN unknown diagnostic attribute +struct Foo; + +fn main() { +} diff --git a/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.stderr b/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.stderr new file mode 100644 index 00000000000..4f9b7ba2bcf --- /dev/null +++ b/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.stderr @@ -0,0 +1,16 @@ +warning: unknown diagnostic attribute + --> $DIR/non_existing_attributes_accepted.rs:3:15 + | +LL | #[diagnostic::non_existing_attribute] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unknown_diagnostic_attributes)]` on by default + +warning: unknown diagnostic attribute + --> $DIR/non_existing_attributes_accepted.rs:8:15 + | +LL | #[diagnostic::non_existing_attribute(with_option = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +warning: 2 warnings emitted + diff --git a/tests/ui/diagnostic_namespace/requires_path.rs b/tests/ui/diagnostic_namespace/requires_path.rs new file mode 100644 index 00000000000..e8d6ca73ad0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/requires_path.rs @@ -0,0 +1,9 @@ +#![feature(diagnostic_namespace)] + +#[diagnostic] +//~^ERROR cannot find attribute `diagnostic` in this scope +pub struct Bar; + + +fn main() { +} diff --git a/tests/ui/diagnostic_namespace/requires_path.stderr b/tests/ui/diagnostic_namespace/requires_path.stderr new file mode 100644 index 00000000000..ce867621daa --- /dev/null +++ b/tests/ui/diagnostic_namespace/requires_path.stderr @@ -0,0 +1,8 @@ +error: cannot find attribute `diagnostic` in this scope + --> $DIR/requires_path.rs:3:3 + | +LL | #[diagnostic] + | ^^^^^^^^^^ + +error: aborting due to previous error +