1
1
#![deny(unused_must_use)]
2
2
3
+ use std::cell::RefCell;
4
+
3
5
use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind};
4
6
use crate::diagnostics::error::{span_err, DiagnosticDeriveError};
5
7
use crate::diagnostics::utils::SetOnce;
@@ -28,6 +30,7 @@ impl<'a> DiagnosticDerive<'a> {
28
30
pub(crate) fn into_tokens(self) -> TokenStream {
29
31
let DiagnosticDerive { mut structure, mut builder } = self;
30
32
33
+ let slugs = RefCell::new(Vec::new());
31
34
let implementation = builder.each_variant(&mut structure, |mut builder, variant| {
32
35
let preamble = builder.preamble(variant);
33
36
let body = builder.body(variant);
@@ -56,6 +59,7 @@ impl<'a> DiagnosticDerive<'a> {
56
59
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
57
60
}
58
61
Some(slug) => {
62
+ slugs.borrow_mut().push(slug.clone());
59
63
quote! {
60
64
let mut #diag = #handler.struct_diagnostic(crate::fluent_generated::#slug);
61
65
}
@@ -73,7 +77,8 @@ impl<'a> DiagnosticDerive<'a> {
73
77
});
74
78
75
79
let DiagnosticDeriveKind::Diagnostic { handler } = &builder.kind else { unreachable!() };
76
- structure.gen_impl(quote! {
80
+ #[allow(unused_mut)]
81
+ let mut imp = structure.gen_impl(quote! {
77
82
gen impl<'__diagnostic_handler_sess, G>
78
83
rustc_errors::IntoDiagnostic<'__diagnostic_handler_sess, G>
79
84
for @Self
@@ -89,7 +94,14 @@ impl<'a> DiagnosticDerive<'a> {
89
94
#implementation
90
95
}
91
96
}
92
- })
97
+ });
98
+ #[cfg(debug_assertions)]
99
+ {
100
+ for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
101
+ imp.extend(test);
102
+ }
103
+ }
104
+ imp
93
105
}
94
106
}
95
107
@@ -124,6 +136,7 @@ impl<'a> LintDiagnosticDerive<'a> {
124
136
}
125
137
});
126
138
139
+ let slugs = RefCell::new(Vec::new());
127
140
let msg = builder.each_variant(&mut structure, |mut builder, variant| {
128
141
// Collect the slug by generating the preamble.
129
142
let _ = builder.preamble(variant);
@@ -148,6 +161,7 @@ impl<'a> LintDiagnosticDerive<'a> {
148
161
DiagnosticDeriveError::ErrorHandled.to_compile_error()
149
162
}
150
163
Some(slug) => {
164
+ slugs.borrow_mut().push(slug.clone());
151
165
quote! {
152
166
crate::fluent_generated::#slug.into()
153
167
}
@@ -156,7 +170,8 @@ impl<'a> LintDiagnosticDerive<'a> {
156
170
});
157
171
158
172
let diag = &builder.diag;
159
- structure.gen_impl(quote! {
173
+ #[allow(unused_mut)]
174
+ let mut imp = structure.gen_impl(quote! {
160
175
gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
161
176
#[track_caller]
162
177
fn decorate_lint<'__b>(
@@ -171,7 +186,14 @@ impl<'a> LintDiagnosticDerive<'a> {
171
186
#msg
172
187
}
173
188
}
174
- })
189
+ });
190
+ #[cfg(debug_assertions)]
191
+ {
192
+ for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
193
+ imp.extend(test);
194
+ }
195
+ }
196
+ imp
175
197
}
176
198
}
177
199
@@ -198,3 +220,41 @@ impl Mismatch {
198
220
}
199
221
}
200
222
}
223
+
224
+ /// Generates a `#[test]` that verifies that all referenced variables
225
+ /// exist on this structure.
226
+ #[cfg(debug_assertions)]
227
+ fn generate_test(slug: &syn::Path, structure: &Structure<'_>) -> TokenStream {
228
+ // FIXME: We can't identify variables in a subdiagnostic
229
+ for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) {
230
+ for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) {
231
+ if attr_name == "subdiagnostic" {
232
+ return quote!();
233
+ }
234
+ }
235
+ }
236
+ use std::sync::atomic::{AtomicUsize, Ordering};
237
+ // We need to make sure that the same diagnostic slug can be used multiple times without causing an
238
+ // error, so just have a global counter here.
239
+ static COUNTER: AtomicUsize = AtomicUsize::new(0);
240
+ let slug = slug.get_ident().unwrap();
241
+ let ident = quote::format_ident!("verify_{slug}_{}", COUNTER.fetch_add(1, Ordering::Relaxed));
242
+ let ref_slug = quote::format_ident!("{slug}_refs");
243
+ let struct_name = &structure.ast().ident;
244
+ let variables: Vec<_> = structure
245
+ .variants()
246
+ .iter()
247
+ .flat_map(|v| v.ast().fields.iter().filter_map(|f| f.ident.as_ref().map(|i| i.to_string())))
248
+ .collect();
249
+ // tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this
250
+ quote! {
251
+ #[cfg(test)]
252
+ #[test ]
253
+ fn #ident() {
254
+ let variables = [#(#variables),*];
255
+ for vref in crate::fluent_generated::#ref_slug {
256
+ assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug));
257
+ }
258
+ }
259
+ }
260
+ }
0 commit comments