]> git.lizzy.rs Git - rust.git/commitdiff
lint: add diagnostic translation migration lints
authorDavid Wood <david.wood@huawei.com>
Fri, 10 Jun 2022 14:50:06 +0000 (15:50 +0100)
committerDavid Wood <david.wood@huawei.com>
Fri, 10 Jun 2022 14:50:06 +0000 (15:50 +0100)
Introduce allow-by-default lints for checking whether diagnostics are
written in `SessionDiagnostic`/`AddSubdiagnostic` impls and whether
diagnostics are translatable. These lints can be denied for modules once
they are fully migrated to impls and translation.

Signed-off-by: David Wood <david.wood@huawei.com>
15 files changed:
compiler/rustc_error_messages/src/lib.rs
compiler/rustc_errors/src/diagnostic.rs
compiler/rustc_errors/src/lib.rs
compiler/rustc_feature/src/builtin_attrs.rs
compiler/rustc_lint/src/internal.rs
compiler/rustc_lint/src/lib.rs
compiler/rustc_passes/src/check_attr.rs
compiler/rustc_session/src/lib.rs
compiler/rustc_session/src/parse.rs
compiler/rustc_session/src/session.rs
compiler/rustc_span/src/symbol.rs
src/test/ui-fulldeps/internal-lints/diagnostics.rs [new file with mode: 0644]
src/test/ui-fulldeps/internal-lints/diagnostics.stderr [new file with mode: 0644]
src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.rs [new file with mode: 0644]
src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.stderr [new file with mode: 0644]

index 02d076c95ca5218d838f69c829f88eb24998da1c..fd4b2daae9c13c236c85c7c52412067a5aa6b78d 100644 (file)
@@ -1,6 +1,7 @@
 #![feature(let_chains)]
 #![feature(once_cell)]
 #![feature(path_try_exists)]
+#![feature(rustc_attrs)]
 #![feature(type_alias_impl_trait)]
 
 use fluent_bundle::FluentResource;
@@ -241,6 +242,7 @@ pub fn fallback_fluent_bundle(
 /// message so messages of this type must be combined with a `DiagnosticMessage` (using
 /// `DiagnosticMessage::with_subdiagnostic_message`) before rendering. However, subdiagnostics from
 /// the `SessionSubdiagnostic` derive refer to Fluent identifiers directly.
+#[rustc_diagnostic_item = "SubdiagnosticMessage"]
 pub enum SubdiagnosticMessage {
     /// Non-translatable diagnostic message.
     // FIXME(davidtwco): can a `Cow<'static, str>` be used here?
@@ -281,6 +283,7 @@ fn from(s: S) -> Self {
 ///
 /// Intended to be removed once diagnostics are entirely translatable.
 #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
+#[rustc_diagnostic_item = "DiagnosticMessage"]
 pub enum DiagnosticMessage {
     /// Non-translatable diagnostic message.
     // FIXME(davidtwco): can a `Cow<'static, str>` be used here?
index eaceecc1667782c049f2aab48a1c1603f36f48a5..00c0ff8bcaf9c6f641a2b4b7a47eaac872170b56 100644 (file)
@@ -80,6 +80,7 @@ fn into(self) -> FluentValue<'source> {
 
 /// Trait implemented by error types. This should not be implemented manually. Instead, use
 /// `#[derive(SessionSubdiagnostic)]` -- see [rustc_macros::SessionSubdiagnostic].
+#[rustc_diagnostic_item = "AddSubdiagnostic"]
 pub trait AddSubdiagnostic {
     /// Add a subdiagnostic to an existing diagnostic.
     fn add_to_diagnostic(self, diag: &mut Diagnostic);
@@ -283,6 +284,7 @@ pub fn downgrade_to_delayed_bug(&mut self) -> &mut Self {
     ///
     /// This span is *not* considered a ["primary span"][`MultiSpan`]; only
     /// the `Span` supplied when creating the diagnostic is primary.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_label(&mut self, span: Span, label: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.span.push_span_label(span, self.subdiagnostic_message_to_diagnostic_message(label));
         self
@@ -401,6 +403,7 @@ pub fn note_trait_signature(&mut self, name: String, signature: String) -> &mut
     }
 
     /// Add a note attached to this diagnostic.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn note(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::Note, msg, MultiSpan::new(), None);
         self
@@ -423,6 +426,7 @@ pub fn note_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
 
     /// Prints the span with a note above it.
     /// This is like [`Diagnostic::note()`], but it gets its own span.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_note<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
@@ -444,6 +448,7 @@ pub fn span_note_once<S: Into<MultiSpan>>(
     }
 
     /// Add a warning attached to this diagnostic.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn warn(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::Warning, msg, MultiSpan::new(), None);
         self
@@ -451,6 +456,7 @@ pub fn warn(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
 
     /// Prints the span with a warning above it.
     /// This is like [`Diagnostic::warn()`], but it gets its own span.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_warn<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
@@ -461,6 +467,7 @@ pub fn span_warn<S: Into<MultiSpan>>(
     }
 
     /// Add a help message attached to this diagnostic.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn help(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::Help, msg, MultiSpan::new(), None);
         self
@@ -474,6 +481,7 @@ pub fn highlighted_help(&mut self, msg: Vec<(String, Style)>) -> &mut Self {
 
     /// Prints the span with some help above it.
     /// This is like [`Diagnostic::help()`], but it gets its own span.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_help<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
index 3be6dd5af75ac7fc8ec541ab34bfb9c67134dafa..4eef00ddff4a38774d1b193d6658abfbbcaa3a5f 100644 (file)
@@ -9,6 +9,7 @@
 #![feature(let_else)]
 #![feature(never_type)]
 #![feature(adt_const_params)]
+#![feature(rustc_attrs)]
 #![allow(incomplete_features)]
 #![allow(rustc::potential_query_instability)]
 
@@ -644,6 +645,7 @@ pub fn emit_stashed_diagnostics(&self) -> Option<ErrorGuaranteed> {
     /// Attempting to `.emit()` the builder will only emit if either:
     /// * `can_emit_warnings` is `true`
     /// * `is_force_warn` was set in `DiagnosticId::Lint`
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_warn(
         &self,
         span: impl Into<MultiSpan>,
@@ -655,6 +657,7 @@ pub fn struct_span_warn(
     }
 
     /// Construct a builder at the `Allow` level at the given `span` and with the `msg`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_allow(
         &self,
         span: impl Into<MultiSpan>,
@@ -667,6 +670,7 @@ pub fn struct_span_allow(
 
     /// Construct a builder at the `Warning` level at the given `span` and with the `msg`.
     /// Also include a code.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_warn_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -683,16 +687,19 @@ pub fn struct_span_warn_with_code(
     /// Attempting to `.emit()` the builder will only emit if either:
     /// * `can_emit_warnings` is `true`
     /// * `is_force_warn` was set in `DiagnosticId::Lint`
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Warning, msg)
     }
 
     /// Construct a builder at the `Allow` level with the `msg`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_allow(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Allow, msg)
     }
 
     /// Construct a builder at the `Expect` level with the `msg`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_expect(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -702,6 +709,7 @@ pub fn struct_expect(
     }
 
     /// Construct a builder at the `Error` level at the given `span` and with the `msg`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_err(
         &self,
         span: impl Into<MultiSpan>,
@@ -713,6 +721,7 @@ pub fn struct_span_err(
     }
 
     /// Construct a builder at the `Error` level at the given `span`, with the `msg`, and `code`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_err_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -726,6 +735,7 @@ pub fn struct_span_err_with_code(
 
     /// Construct a builder at the `Error` level with the `msg`.
     // FIXME: This method should be removed (every error should have an associated error code).
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_err(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -740,6 +750,7 @@ pub fn struct_err_lint(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBu
     }
 
     /// Construct a builder at the `Error` level with the `msg` and the `code`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_err_with_code(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -751,6 +762,7 @@ pub fn struct_err_with_code(
     }
 
     /// Construct a builder at the `Warn` level with the `msg` and the `code`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_warn_with_code(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -762,6 +774,7 @@ pub fn struct_warn_with_code(
     }
 
     /// Construct a builder at the `Fatal` level at the given `span` and with the `msg`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_fatal(
         &self,
         span: impl Into<MultiSpan>,
@@ -773,6 +786,7 @@ pub fn struct_span_fatal(
     }
 
     /// Construct a builder at the `Fatal` level at the given `span`, with the `msg`, and `code`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_span_fatal_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -785,16 +799,19 @@ pub fn struct_span_fatal_with_code(
     }
 
     /// Construct a builder at the `Error` level with the `msg`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_fatal(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, !> {
         DiagnosticBuilder::new_fatal(self, msg)
     }
 
     /// Construct a builder at the `Help` level with the `msg`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_help(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Help, msg)
     }
 
     /// Construct a builder at the `Note` level with the `msg`.
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_note_without_error(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -802,11 +819,13 @@ pub fn struct_note_without_error(
         DiagnosticBuilder::new(self, Level::Note, msg)
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_fatal(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
         self.emit_diag_at_span(Diagnostic::new(Fatal, msg), span);
         FatalError.raise()
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_fatal_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -817,6 +836,7 @@ pub fn span_fatal_with_code(
         FatalError.raise()
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_err(
         &self,
         span: impl Into<MultiSpan>,
@@ -825,6 +845,7 @@ pub fn span_err(
         self.emit_diag_at_span(Diagnostic::new(Error { lint: false }, msg), span).unwrap()
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_err_with_code(
         &self,
         span: impl Into<MultiSpan>,
@@ -837,10 +858,12 @@ pub fn span_err_with_code(
         );
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_warn(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) {
         self.emit_diag_at_span(Diagnostic::new(Warning, msg), span);
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn span_warn_with_code(
         &self,
         span: impl Into<MultiSpan>,
index 5eb2be97f8b92b80fd372f7d00015263fba568d3..34c53597dde6b8df09a3d12f22b5e05aba2d8267 100644 (file)
@@ -615,6 +615,9 @@ pub struct BuiltinAttribute {
     // Used by the `rustc::potential_query_instability` lint to warn methods which
     // might not be stable during incremental compilation.
     rustc_attr!(rustc_lint_query_instability, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE),
+    // Used by the `rustc::untranslatable_diagnostic` and `rustc::diagnostic_outside_of_impl` lints
+    // to assist in changes to diagnostic APIs.
+    rustc_attr!(rustc_lint_diagnostics, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE),
 
     // ==========================================================================
     // Internal attributes, Const related:
index b83d63e0da086008b00cb320ddb60174866e458e..e70bcaac0c830a70f98b17e991d41c9c5208d7ef 100644 (file)
@@ -5,12 +5,14 @@
 use rustc_ast as ast;
 use rustc_errors::Applicability;
 use rustc_hir::def::Res;
-use rustc_hir::{Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
-use rustc_hir::{HirId, Item, ItemKind, Node, Pat, Ty, TyKind};
+use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
+use rustc_hir::{HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
 use rustc_middle::ty;
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::hygiene::{ExpnKind, MacroKind};
 use rustc_span::symbol::{kw, sym, Symbol};
+use rustc_span::Span;
+use tracing::debug;
 
 declare_tool_lint! {
     pub rustc::DEFAULT_HASH_TYPES,
@@ -46,6 +48,41 @@ fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
     }
 }
 
+/// Helper function for lints that check for expressions with calls and use typeck results to
+/// get the `DefId` and `SubstsRef` of the function.
+fn typeck_results_of_method_fn<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &Expr<'_>,
+) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> {
+    // FIXME(rustdoc): Lints which use this function use typecheck results which can cause
+    // `rustdoc` to error if there are resolution failures.
+    //
+    // As internal lints are currently always run if there are `unstable_options`, they are added
+    // to the lint store of rustdoc. Internal lints are also not used via the `lint_mod` query.
+    // Crate lints run outside of a query so rustdoc currently doesn't disable them.
+    //
+    // Instead of relying on this, either change crate lints to a query disabled by rustdoc, only
+    // run internal lints if the user is explicitly opting in or figure out a different way to
+    // avoid running lints for rustdoc.
+    if cx.tcx.sess.opts.actually_rustdoc {
+        return None;
+    }
+
+    match expr.kind {
+        ExprKind::MethodCall(segment, _, _)
+            if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
+        {
+            Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id)))
+        },
+        _ => {
+            match cx.typeck_results().node_type(expr.hir_id).kind() {
+                &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)),
+                _ => None,
+            }
+        }
+    }
+}
+
 declare_tool_lint! {
     pub rustc::POTENTIAL_QUERY_INSTABILITY,
     Allow,
@@ -57,35 +94,7 @@ fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
 
 impl LateLintPass<'_> for QueryStability {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
-        // FIXME(rustdoc): This lint uses typecheck results, causing rustdoc to
-        // error if there are resolution failures.
-        //
-        // As internal lints are currently always run if there are `unstable_options`,
-        // they are added to the lint store of rustdoc. Internal lints are also
-        // not used via the `lint_mod` query. Crate lints run outside of a query
-        // so rustdoc currently doesn't disable them.
-        //
-        // Instead of relying on this, either change crate lints to a query disabled by
-        // rustdoc, only run internal lints if the user is explicitly opting in
-        // or figure out a different way to avoid running lints for rustdoc.
-        if cx.tcx.sess.opts.actually_rustdoc {
-            return;
-        }
-
-        let (span, def_id, substs) = match expr.kind {
-            ExprKind::MethodCall(segment, _, _)
-                if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
-            {
-                (segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id))
-            },
-            _ => {
-                let &ty::FnDef(def_id, substs) =
-                    cx.typeck_results()
-                        .node_type(expr.hir_id)
-                        .kind() else { return };
-                (expr.span, def_id, substs)
-            }
-        };
+        let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
         if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) {
             let def_id = instance.def_id();
             if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
@@ -376,3 +385,70 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
         }
     }
 }
+
+declare_tool_lint! {
+    pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
+    Allow,
+    "prevent creation of diagnostics which cannot be translated",
+    report_in_external_macro: true
+}
+
+declare_tool_lint! {
+    pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
+    Allow,
+    "prevent creation of diagnostics outside of `SessionDiagnostic`/`AddSubdiagnostic` impls",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]);
+
+impl LateLintPass<'_> for Diagnostics {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+        let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
+        debug!(?span, ?def_id, ?substs);
+        if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) &&
+            !cx.tcx.has_attr(instance.def_id(), sym::rustc_lint_diagnostics)
+        {
+            return;
+        }
+
+        let mut found_impl = false;
+        for (_, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
+            debug!(?parent);
+            if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent &&
+                let Impl { of_trait: Some(of_trait), .. } = impl_ &&
+                let Some(def_id) = of_trait.trait_def_id() &&
+                let Some(name) = cx.tcx.get_diagnostic_name(def_id) &&
+                matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic)
+            {
+                found_impl = true;
+                break;
+            }
+        }
+        debug!(?found_impl);
+        if !found_impl {
+            cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| {
+                lint.build("diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls")
+                    .emit();
+            })
+        }
+
+        let mut found_diagnostic_message = false;
+        for ty in substs.types() {
+            debug!(?ty);
+            if let Some(adt_def) = ty.ty_adt_def() &&
+                let Some(name) =  cx.tcx.get_diagnostic_name(adt_def.did()) &&
+                matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage)
+            {
+                found_diagnostic_message = true;
+                break;
+            }
+        }
+        debug!(?found_diagnostic_message);
+        if !found_diagnostic_message {
+            cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| {
+                lint.build("diagnostics should be created using translatable messages").emit();
+            })
+        }
+    }
+}
index ff4ed94fab339f9906fee1ad454d510db8ac2615..f0182883d2b46ae5747385bd63eec991f23bdc85 100644 (file)
@@ -508,6 +508,8 @@ fn register_internals(store: &mut LintStore) {
     store.register_late_pass(|| Box::new(ExistingDocKeyword));
     store.register_lints(&TyTyKind::get_lints());
     store.register_late_pass(|| Box::new(TyTyKind));
+    store.register_lints(&Diagnostics::get_lints());
+    store.register_late_pass(|| Box::new(Diagnostics));
     store.register_lints(&PassByValue::get_lints());
     store.register_late_pass(|| Box::new(PassByValue));
     store.register_group(
index 5cc97d326d3d82cb2caf72f88bda9ab038dffba5..e3a02cb8161f8cbb919e79d3ef343beefbf85014 100644 (file)
@@ -118,6 +118,9 @@ fn check_attributes(
                 sym::rustc_lint_query_instability => {
                     self.check_rustc_lint_query_instability(&attr, span, target)
                 }
+                sym::rustc_lint_diagnostics => {
+                    self.check_rustc_lint_diagnostics(&attr, span, target)
+                }
                 sym::rustc_clean
                 | sym::rustc_dirty
                 | sym::rustc_if_this_changed
@@ -1624,12 +1627,9 @@ fn check_rustc_legacy_const_generics(
         }
     }
 
-    fn check_rustc_lint_query_instability(
-        &self,
-        attr: &Attribute,
-        span: Span,
-        target: Target,
-    ) -> bool {
+    /// Helper function for checking that the provided attribute is only applied to a function or
+    /// method.
+    fn check_applied_to_fn_or_method(&self, attr: &Attribute, span: Span, target: Target) -> bool {
         let is_function = matches!(target, Target::Fn | Target::Method(..));
         if !is_function {
             self.tcx
@@ -1643,6 +1643,23 @@ fn check_rustc_lint_query_instability(
         }
     }
 
+    /// Checks that the `#[rustc_lint_query_instability]` attribute is only applied to a function
+    /// or method.
+    fn check_rustc_lint_query_instability(
+        &self,
+        attr: &Attribute,
+        span: Span,
+        target: Target,
+    ) -> bool {
+        self.check_applied_to_fn_or_method(attr, span, target)
+    }
+
+    /// Checks that the `#[rustc_lint_diagnostics]` attribute is only applied to a function or
+    /// method.
+    fn check_rustc_lint_diagnostics(&self, attr: &Attribute, span: Span, target: Target) -> bool {
+        self.check_applied_to_fn_or_method(attr, span, target)
+    }
+
     /// Checks that the dep-graph debugging attributes are only present when the query-dep-graph
     /// option is passed to the compiler.
     fn check_rustc_dirty_clean(&self, attr: &Attribute) -> bool {
index 59b4981dd0120a2add641a9cbd00d471cbfd8777..044be906b55071c21cc7b2f7521f9917c66c386a 100644 (file)
@@ -5,6 +5,7 @@
 #![feature(never_type)]
 #![feature(once_cell)]
 #![feature(option_get_or_insert_default)]
+#![feature(rustc_attrs)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
 
index 6fb87e15a3303a6ce105be77189b2953f5fe6dc4..a5ccae047fcc311cbbb34010d21e9526f5cfc043 100644 (file)
@@ -311,6 +311,7 @@ pub fn emit_warning<'a>(&'a self, warning: impl SessionDiagnostic<'a, ()>) {
         self.create_warning(warning).emit()
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_err(
         &self,
         msg: impl Into<DiagnosticMessage>,
@@ -318,6 +319,7 @@ pub fn struct_err(
         self.span_diagnostic.struct_err(msg)
     }
 
+    #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)]
     pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         self.span_diagnostic.struct_warn(msg)
     }
index b2c23cda6aae545b1bcf2f893eb63dd671d90fa7..b1d1f9e7a6ce013f20ef9f15e5a7ac5acb09e283 100644 (file)
@@ -209,6 +209,7 @@ pub struct PerfStats {
 
 /// Trait implemented by error types. This should not be implemented manually. Instead, use
 /// `#[derive(SessionDiagnostic)]` -- see [rustc_macros::SessionDiagnostic].
+#[rustc_diagnostic_item = "SessionDiagnostic"]
 pub trait SessionDiagnostic<'a, T: EmissionGuarantee = ErrorGuaranteed> {
     /// Write out as a diagnostic out of `sess`.
     #[must_use]
index 7b0fa65e8086be144b322533a43f2e4b2f6ce880..6547ec493c8620292a1b2bb8ed1d945c0bb867ba 100644 (file)
     Symbols {
         AcqRel,
         Acquire,
+        AddSubdiagnostic,
         Alignment,
         Any,
         Arc,
         Decoder,
         Default,
         Deref,
+        DiagnosticMessage,
         DirBuilder,
         Display,
         DoubleEndedIterator,
         RustcEncodable,
         Send,
         SeqCst,
+        SessionDiagnostic,
         SliceIndex,
         Some,
         String,
         StructuralEq,
         StructuralPartialEq,
+        SubdiagnosticMessage,
         Sync,
         Target,
         ToOwned,
         rustc_layout_scalar_valid_range_end,
         rustc_layout_scalar_valid_range_start,
         rustc_legacy_const_generics,
+        rustc_lint_diagnostics,
         rustc_lint_query_instability,
         rustc_macro_transparency,
         rustc_main,
diff --git a/src/test/ui-fulldeps/internal-lints/diagnostics.rs b/src/test/ui-fulldeps/internal-lints/diagnostics.rs
new file mode 100644 (file)
index 0000000..817d853
--- /dev/null
@@ -0,0 +1,73 @@
+// compile-flags: -Z unstable-options
+
+#![crate_type = "lib"]
+#![feature(rustc_private)]
+#![deny(rustc::untranslatable_diagnostic)]
+#![deny(rustc::diagnostic_outside_of_impl)]
+
+extern crate rustc_errors;
+extern crate rustc_macros;
+extern crate rustc_session;
+extern crate rustc_span;
+
+use rustc_errors::{AddSubdiagnostic, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, fluent};
+use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic};
+use rustc_session::{parse::ParseSess, SessionDiagnostic};
+use rustc_span::Span;
+
+#[derive(SessionDiagnostic)]
+#[error(slug = "parser-expect-path")]
+struct DeriveSessionDiagnostic {
+    #[primary_span]
+    span: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[note(slug = "note")]
+struct Note {
+    #[primary_span]
+    span: Span,
+}
+
+pub struct UntranslatableInSessionDiagnostic;
+
+impl<'a> SessionDiagnostic<'a, ErrorGuaranteed> for UntranslatableInSessionDiagnostic {
+    fn into_diagnostic(self, sess: &'a ParseSess) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
+        sess.struct_err("untranslatable diagnostic")
+        //~^ ERROR diagnostics should be created using translatable messages
+    }
+}
+
+pub struct TranslatableInSessionDiagnostic;
+
+impl<'a> SessionDiagnostic<'a, ErrorGuaranteed> for TranslatableInSessionDiagnostic {
+    fn into_diagnostic(self, sess: &'a ParseSess) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
+        sess.struct_err(fluent::parser::expect_path)
+    }
+}
+
+pub struct UntranslatableInAddSubdiagnostic;
+
+impl AddSubdiagnostic for UntranslatableInAddSubdiagnostic {
+    fn add_to_diagnostic(self, diag: &mut Diagnostic) {
+        diag.note("untranslatable diagnostic");
+        //~^ ERROR diagnostics should be created using translatable messages
+    }
+}
+
+pub struct TranslatableInAddSubdiagnostic;
+
+impl AddSubdiagnostic for TranslatableInAddSubdiagnostic {
+    fn add_to_diagnostic(self, diag: &mut Diagnostic) {
+        diag.note(fluent::typeck::note);
+    }
+}
+
+pub fn make_diagnostics<'a>(sess: &'a ParseSess) {
+    let _diag = sess.struct_err(fluent::parser::expect_path);
+    //~^ ERROR diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls
+
+    let _diag = sess.struct_err("untranslatable diagnostic");
+    //~^ ERROR diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls
+    //~^^ ERROR diagnostics should be created using translatable messages
+}
diff --git a/src/test/ui-fulldeps/internal-lints/diagnostics.stderr b/src/test/ui-fulldeps/internal-lints/diagnostics.stderr
new file mode 100644 (file)
index 0000000..bae78ff
--- /dev/null
@@ -0,0 +1,44 @@
+error: diagnostics should be created using translatable messages
+  --> $DIR/diagnostics.rs:36:14
+   |
+LL |         sess.struct_err("untranslatable diagnostic")
+   |              ^^^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/diagnostics.rs:5:9
+   |
+LL | #![deny(rustc::untranslatable_diagnostic)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: diagnostics should be created using translatable messages
+  --> $DIR/diagnostics.rs:53:14
+   |
+LL |         diag.note("untranslatable diagnostic");
+   |              ^^^^
+
+error: diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls
+  --> $DIR/diagnostics.rs:67:22
+   |
+LL |     let _diag = sess.struct_err(fluent::parser::expect_path);
+   |                      ^^^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/diagnostics.rs:6:9
+   |
+LL | #![deny(rustc::diagnostic_outside_of_impl)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls
+  --> $DIR/diagnostics.rs:70:22
+   |
+LL |     let _diag = sess.struct_err("untranslatable diagnostic");
+   |                      ^^^^^^^^^^
+
+error: diagnostics should be created using translatable messages
+  --> $DIR/diagnostics.rs:70:22
+   |
+LL |     let _diag = sess.struct_err("untranslatable diagnostic");
+   |                      ^^^^^^^^^^
+
+error: aborting due to 5 previous errors
+
diff --git a/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.rs b/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.rs
new file mode 100644 (file)
index 0000000..99f99ff
--- /dev/null
@@ -0,0 +1,15 @@
+// compile-flags: -Z unstable-options
+
+#![feature(rustc_attrs)]
+
+#[rustc_lint_diagnostics]
+//~^ ERROR attribute should be applied to a function
+struct Foo;
+
+impl Foo {
+    #[rustc_lint_diagnostics(a)]
+    //~^ ERROR malformed `rustc_lint_diagnostics`
+    fn bar() {}
+}
+
+fn main() {}
diff --git a/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.stderr b/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.stderr
new file mode 100644 (file)
index 0000000..46c206f
--- /dev/null
@@ -0,0 +1,17 @@
+error: malformed `rustc_lint_diagnostics` attribute input
+  --> $DIR/diagnostics_incorrect.rs:10:5
+   |
+LL |     #[rustc_lint_diagnostics(a)]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_lint_diagnostics]`
+
+error: attribute should be applied to a function
+  --> $DIR/diagnostics_incorrect.rs:5:1
+   |
+LL | #[rustc_lint_diagnostics]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |
+LL | struct Foo;
+   | ----------- not a function
+
+error: aborting due to 2 previous errors
+