Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9d864c8

Browse files
committedJul 5, 2022
macros: add diagnostic derive for lints
`SessionDiagnostic` isn't suitable for use on lints as whether or not it creates an error or a warning is decided at compile-time by the macro, whereas lints decide this at runtime based on the location of the lint being reported (as it will depend on the user's `allow`/`deny` attributes, etc). Re-using most of the machinery for `SessionDiagnostic`, this macro introduces a `LintDiagnostic` derive which implements a `DecorateLint` trait, taking a `LintDiagnosticBuilder` and adding to the lint according to the diagnostic struct.
1 parent 7f9d848 commit 9d864c8

File tree

12 files changed

+845
-612
lines changed

12 files changed

+845
-612
lines changed
 

‎Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4009,6 +4009,7 @@ dependencies = [
40094009
"rustc_hir",
40104010
"rustc_index",
40114011
"rustc_infer",
4012+
"rustc_macros",
40124013
"rustc_middle",
40134014
"rustc_parse_format",
40144015
"rustc_session",

‎compiler/rustc_lint/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ rustc_trait_selection = { path = "../rustc_trait_selection" }
2222
rustc_parse_format = { path = "../rustc_parse_format" }
2323
rustc_infer = { path = "../rustc_infer" }
2424
rustc_type_ir = { path = "../rustc_type_ir" }
25+
rustc_macros = { path = "../rustc_macros" }

‎compiler/rustc_lint/src/types.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc_data_structures::fx::FxHashSet;
55
use rustc_errors::{fluent, Applicability, DiagnosticMessage};
66
use rustc_hir as hir;
77
use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
8+
use rustc_macros::LintDiagnostic;
89
use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton};
910
use rustc_middle::ty::subst::SubstsRef;
1011
use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable};
@@ -1553,13 +1554,20 @@ impl InvalidAtomicOrdering {
15531554
let Some(fail_ordering) = Self::match_ordering(cx, fail_order_arg) else { return };
15541555

15551556
if matches!(fail_ordering, sym::Release | sym::AcqRel) {
1556-
cx.struct_span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg.span, |diag| {
1557-
diag.build(fluent::lint::atomic_ordering_invalid)
1558-
.set_arg("method", method)
1559-
.span_label(fail_order_arg.span, fluent::lint::label)
1560-
.help(fluent::lint::help)
1561-
.emit();
1562-
});
1557+
#[derive(LintDiagnostic)]
1558+
#[lint(lint::atomic_ordering_invalid)]
1559+
#[help]
1560+
struct InvalidAtomicOrderingDiag {
1561+
method: Symbol,
1562+
#[label]
1563+
fail_order_arg_span: Span,
1564+
}
1565+
1566+
cx.emit_spanned_lint(
1567+
INVALID_ATOMIC_ORDERING,
1568+
fail_order_arg.span,
1569+
InvalidAtomicOrderingDiag { method, fail_order_arg_span: fail_order_arg.span },
1570+
);
15631571
}
15641572

15651573
let Some(success_ordering) = Self::match_ordering(cx, success_order_arg) else { return };

‎compiler/rustc_macros/src/diagnostics/diagnostic.rs

Lines changed: 102 additions & 575 deletions
Large diffs are not rendered by default.

‎compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs

Lines changed: 590 additions & 0 deletions
Large diffs are not rendered by default.

‎compiler/rustc_macros/src/diagnostics/error.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ use quote::quote;
44
use syn::{spanned::Spanned, Attribute, Error as SynError, Meta, NestedMeta};
55

66
#[derive(Debug)]
7-
pub(crate) enum SessionDiagnosticDeriveError {
7+
pub(crate) enum DiagnosticDeriveError {
88
SynError(SynError),
99
ErrorHandled,
1010
}
1111

12-
impl SessionDiagnosticDeriveError {
12+
impl DiagnosticDeriveError {
1313
pub(crate) fn to_compile_error(self) -> TokenStream {
1414
match self {
15-
SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(),
16-
SessionDiagnosticDeriveError::ErrorHandled => {
15+
DiagnosticDeriveError::SynError(e) => e.to_compile_error(),
16+
DiagnosticDeriveError::ErrorHandled => {
1717
// Return ! to avoid having to create a blank DiagnosticBuilder to return when an
1818
// error has already been emitted to the compiler.
1919
quote! {
@@ -24,19 +24,19 @@ impl SessionDiagnosticDeriveError {
2424
}
2525
}
2626

27-
impl From<SynError> for SessionDiagnosticDeriveError {
27+
impl From<SynError> for DiagnosticDeriveError {
2828
fn from(e: SynError) -> Self {
29-
SessionDiagnosticDeriveError::SynError(e)
29+
DiagnosticDeriveError::SynError(e)
3030
}
3131
}
3232

3333
/// Helper function for use with `throw_*` macros - constraints `$f` to an `impl FnOnce`.
3434
pub(crate) fn _throw_err(
3535
diag: Diagnostic,
3636
f: impl FnOnce(Diagnostic) -> Diagnostic,
37-
) -> SessionDiagnosticDeriveError {
37+
) -> DiagnosticDeriveError {
3838
f(diag).emit();
39-
SessionDiagnosticDeriveError::ErrorHandled
39+
DiagnosticDeriveError::ErrorHandled
4040
}
4141

4242
/// Helper function for printing `syn::Path` - doesn't handle arguments in paths and these are
@@ -60,7 +60,7 @@ pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
6060
/// Emit a diagnostic on span `$span` with msg `$msg` (optionally performing additional decoration
6161
/// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
6262
///
63-
/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`:
63+
/// For methods that return a `Result<_, DiagnosticDeriveError>`:
6464
macro_rules! throw_span_err {
6565
($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
6666
($span:expr, $msg:expr, $f:expr) => {{
@@ -87,7 +87,7 @@ pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic {
8787
/// Emit a error diagnostic for an invalid attribute (optionally performing additional decoration
8888
/// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
8989
///
90-
/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`:
90+
/// For methods that return a `Result<_, DiagnosticDeriveError>`:
9191
macro_rules! throw_invalid_attr {
9292
($attr:expr, $meta:expr) => {{ throw_invalid_attr!($attr, $meta, |diag| diag) }};
9393
($attr:expr, $meta:expr, $f:expr) => {{
@@ -129,7 +129,7 @@ pub(crate) fn invalid_nested_attr(attr: &Attribute, nested: &NestedMeta) -> Diag
129129
/// Emit a error diagnostic for an invalid nested attribute (optionally performing additional
130130
/// decoration using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
131131
///
132-
/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`:
132+
/// For methods that return a `Result<_, DiagnosticDeriveError>`:
133133
macro_rules! throw_invalid_nested_attr {
134134
($attr:expr, $nested_attr:expr) => {{ throw_invalid_nested_attr!($attr, $nested_attr, |diag| diag) }};
135135
($attr:expr, $nested_attr:expr, $f:expr) => {{

‎compiler/rustc_macros/src/diagnostics/mod.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
mod diagnostic;
2+
mod diagnostic_builder;
23
mod error;
34
mod fluent;
45
mod subdiagnostic;
56
mod utils;
67

7-
use diagnostic::SessionDiagnosticDerive;
8+
use diagnostic::{LintDiagnosticDerive, SessionDiagnosticDerive};
89
pub(crate) use fluent::fluent_messages;
910
use proc_macro2::TokenStream;
1011
use quote::format_ident;
@@ -58,11 +59,53 @@ use synstructure::Structure;
5859
/// See rustc dev guide for more examples on using the `#[derive(SessionDiagnostic)]`:
5960
/// <https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html>
6061
pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
61-
// Names for the diagnostic we build and the session we build it from.
62-
let diag = format_ident!("diag");
63-
let sess = format_ident!("sess");
62+
SessionDiagnosticDerive::new(format_ident!("diag"), format_ident!("sess"), s).into_tokens()
63+
}
6464

65-
SessionDiagnosticDerive::new(diag, sess, s).into_tokens()
65+
/// Implements `#[derive(LintDiagnostic)]`, which allows for lints to be specified as a struct,
66+
/// independent from the actual lint emitting code.
67+
///
68+
/// ```ignore (rust)
69+
/// #[derive(LintDiagnostic)]
70+
/// #[lint(lint::atomic_ordering_invalid_fail_success)]
71+
/// pub struct AtomicOrderingInvalidLint {
72+
/// method: Symbol,
73+
/// success_ordering: Symbol,
74+
/// fail_ordering: Symbol,
75+
/// #[label(lint::fail_label)]
76+
/// fail_order_arg_span: Span,
77+
/// #[label(lint::success_label)]
78+
/// #[suggestion(
79+
/// code = "std::sync::atomic::Ordering::{success_suggestion}",
80+
/// applicability = "maybe-incorrect"
81+
/// )]
82+
/// success_order_arg_span: Span,
83+
/// }
84+
/// ```
85+
///
86+
/// ```fluent
87+
/// lint-atomic-ordering-invalid-fail-success = `{$method}`'s success ordering must be at least as strong as its failure ordering
88+
/// .fail-label = `{$fail_ordering}` failure ordering
89+
/// .success-label = `{$success_ordering}` success ordering
90+
/// .suggestion = consider using `{$success_suggestion}` success ordering instead
91+
/// ```
92+
///
93+
/// Then, later, to emit the error:
94+
///
95+
/// ```ignore (rust)
96+
/// cx.struct_span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg_span, AtomicOrderingInvalidLint {
97+
/// method,
98+
/// success_ordering,
99+
/// fail_ordering,
100+
/// fail_order_arg_span,
101+
/// success_order_arg_span,
102+
/// });
103+
/// ```
104+
///
105+
/// See rustc dev guide for more examples on using the `#[derive(LintDiagnostic)]`:
106+
/// <https://rustc-dev-guide.rust-lang.org/diagnostics/sessiondiagnostic.html>
107+
pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream {
108+
LintDiagnosticDerive::new(format_ident!("diag"), s).into_tokens()
66109
}
67110

68111
/// Implements `#[derive(SessionSubdiagnostic)]`, which allows for labels, notes, helps and

‎compiler/rustc_macros/src/diagnostics/subdiagnostic.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#![deny(unused_must_use)]
22

33
use crate::diagnostics::error::{
4-
span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
5-
SessionDiagnosticDeriveError,
4+
span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
65
};
76
use crate::diagnostics::utils::{
87
report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
@@ -214,7 +213,7 @@ impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
214213
}
215214

216215
impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
217-
fn identify_kind(&mut self) -> Result<(), SessionDiagnosticDeriveError> {
216+
fn identify_kind(&mut self) -> Result<(), DiagnosticDeriveError> {
218217
for attr in self.variant.ast().attrs {
219218
let span = attr.span().unwrap();
220219

@@ -351,7 +350,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
351350
&mut self,
352351
binding: &BindingInfo<'_>,
353352
is_suggestion: bool,
354-
) -> Result<TokenStream, SessionDiagnosticDeriveError> {
353+
) -> Result<TokenStream, DiagnosticDeriveError> {
355354
let ast = binding.ast();
356355

357356
let inner_ty = FieldInnerTy::from_type(&ast.ty);
@@ -411,7 +410,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
411410
Ok(inner_ty.with(binding, generated))
412411
}
413412

414-
fn into_tokens(&mut self) -> Result<TokenStream, SessionDiagnosticDeriveError> {
413+
fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
415414
self.identify_kind()?;
416415
let Some(kind) = self.kind.map(|(kind, _)| kind) else {
417416
throw_span_err!(

‎compiler/rustc_macros/src/diagnostics/utils.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::diagnostics::error::{span_err, throw_span_err, SessionDiagnosticDeriveError};
1+
use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError};
22
use proc_macro::Span;
33
use proc_macro2::TokenStream;
44
use quote::{format_ident, quote, ToTokens};
@@ -34,7 +34,7 @@ pub(crate) fn type_is_unit(ty: &Type) -> bool {
3434
pub(crate) fn report_type_error(
3535
attr: &Attribute,
3636
ty_name: &str,
37-
) -> Result<!, SessionDiagnosticDeriveError> {
37+
) -> Result<!, DiagnosticDeriveError> {
3838
let name = attr.path.segments.last().unwrap().ident.to_string();
3939
let meta = attr.parse_meta()?;
4040

@@ -59,7 +59,7 @@ fn report_error_if_not_applied_to_ty(
5959
info: &FieldInfo<'_>,
6060
path: &[&str],
6161
ty_name: &str,
62-
) -> Result<(), SessionDiagnosticDeriveError> {
62+
) -> Result<(), DiagnosticDeriveError> {
6363
if !type_matches_path(&info.ty, path) {
6464
report_type_error(attr, ty_name)?;
6565
}
@@ -71,7 +71,7 @@ fn report_error_if_not_applied_to_ty(
7171
pub(crate) fn report_error_if_not_applied_to_applicability(
7272
attr: &Attribute,
7373
info: &FieldInfo<'_>,
74-
) -> Result<(), SessionDiagnosticDeriveError> {
74+
) -> Result<(), DiagnosticDeriveError> {
7575
report_error_if_not_applied_to_ty(
7676
attr,
7777
info,
@@ -84,7 +84,7 @@ pub(crate) fn report_error_if_not_applied_to_applicability(
8484
pub(crate) fn report_error_if_not_applied_to_span(
8585
attr: &Attribute,
8686
info: &FieldInfo<'_>,
87-
) -> Result<(), SessionDiagnosticDeriveError> {
87+
) -> Result<(), DiagnosticDeriveError> {
8888
report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "`Span`")
8989
}
9090

@@ -166,10 +166,12 @@ pub(crate) struct FieldInfo<'a> {
166166
/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
167167
/// for error reporting if they are set more than once.
168168
pub(crate) trait SetOnce<T> {
169-
fn set_once(&mut self, value: T);
169+
fn set_once(&mut self, _: (T, Span));
170+
171+
fn value(self) -> Option<T>;
170172
}
171173

172-
impl<T> SetOnce<(T, Span)> for Option<(T, Span)> {
174+
impl<T> SetOnce<T> for Option<(T, Span)> {
173175
fn set_once(&mut self, (value, span): (T, Span)) {
174176
match self {
175177
None => {
@@ -182,6 +184,10 @@ impl<T> SetOnce<(T, Span)> for Option<(T, Span)> {
182184
}
183185
}
184186
}
187+
188+
fn value(self) -> Option<T> {
189+
self.map(|(v, _)| v)
190+
}
185191
}
186192

187193
pub(crate) trait HasFieldMap {

‎compiler/rustc_macros/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ decl_derive!(
127127
// struct attributes
128128
warning,
129129
error,
130+
lint,
130131
note,
131132
help,
132133
// field attributes
@@ -139,6 +140,24 @@ decl_derive!(
139140
suggestion_hidden,
140141
suggestion_verbose)] => diagnostics::session_diagnostic_derive
141142
);
143+
decl_derive!(
144+
[LintDiagnostic, attributes(
145+
// struct attributes
146+
warning,
147+
error,
148+
lint,
149+
note,
150+
help,
151+
// field attributes
152+
skip_arg,
153+
primary_span,
154+
label,
155+
subdiagnostic,
156+
suggestion,
157+
suggestion_short,
158+
suggestion_hidden,
159+
suggestion_verbose)] => diagnostics::lint_diagnostic_derive
160+
);
142161
decl_derive!(
143162
[SessionSubdiagnostic, attributes(
144163
// struct/variant attributes

‎src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_span::symbol::Ident;
1717
use rustc_span::Span;
1818

1919
extern crate rustc_macros;
20-
use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic};
20+
use rustc_macros::{SessionDiagnostic, LintDiagnostic, SessionSubdiagnostic};
2121

2222
extern crate rustc_middle;
2323
use rustc_middle::ty::Ty;
@@ -535,3 +535,20 @@ struct LabelWithTrailingList {
535535
//~^ ERROR `#[label(...)]` is not a valid attribute
536536
span: Span,
537537
}
538+
539+
#[derive(SessionDiagnostic)]
540+
#[lint(typeck::ambiguous_lifetime_bound)]
541+
//~^ ERROR only `#[error(..)]` and `#[warn(..)]` are supported
542+
struct LintsBad {
543+
}
544+
545+
#[derive(LintDiagnostic)]
546+
#[lint(typeck::ambiguous_lifetime_bound)]
547+
struct LintsGood {
548+
}
549+
550+
#[derive(LintDiagnostic)]
551+
#[error(typeck::ambiguous_lifetime_bound)]
552+
//~^ ERROR only `#[lint(..)]` is supported
553+
struct ErrorsBad {
554+
}

‎src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,28 @@ error: `#[label(...)]` is not a valid attribute
363363
LL | #[label(typeck::label, foo("..."))]
364364
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
365365

366+
error: only `#[error(..)]` and `#[warn(..)]` are supported
367+
--> $DIR/diagnostic-derive.rs:540:1
368+
|
369+
LL | / #[lint(typeck::ambiguous_lifetime_bound)]
370+
LL | |
371+
LL | | struct LintsBad {
372+
LL | | }
373+
| |_^
374+
|
375+
= help: use the `#[error(...)]` attribute to create a error
376+
377+
error: only `#[lint(..)]` is supported
378+
--> $DIR/diagnostic-derive.rs:551:1
379+
|
380+
LL | / #[error(typeck::ambiguous_lifetime_bound)]
381+
LL | |
382+
LL | | struct ErrorsBad {
383+
LL | | }
384+
| |_^
385+
|
386+
= help: use the `#[lint(...)]` attribute to create a lint
387+
366388
error: cannot find attribute `nonsense` in this scope
367389
--> $DIR/diagnostic-derive.rs:53:3
368390
|
@@ -395,7 +417,7 @@ LL | arg: impl IntoDiagnosticArg,
395417
| ^^^^^^^^^^^^^^^^^ required by this bound in `DiagnosticBuilder::<'a, G>::set_arg`
396418
= note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
397419

398-
error: aborting due to 46 previous errors
420+
error: aborting due to 48 previous errors
399421

400422
Some errors have detailed explanations: E0277, E0425.
401423
For more information about an error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)
Please sign in to comment.