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