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
+
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,11 @@ impl<'a> DiagnosticDerive<'a> {
89
94
#implementation
90
95
}
91
96
}
92
- } )
97
+ } ) ;
98
+ for test in slugs. borrow ( ) . iter ( ) . map ( |s| generate_test ( s, & structure) ) {
99
+ imp. extend ( test) ;
100
+ }
101
+ imp
93
102
}
94
103
}
95
104
@@ -124,6 +133,7 @@ impl<'a> LintDiagnosticDerive<'a> {
124
133
}
125
134
} ) ;
126
135
136
+ let slugs = RefCell :: new ( Vec :: new ( ) ) ;
127
137
let msg = builder. each_variant ( & mut structure, |mut builder, variant| {
128
138
// Collect the slug by generating the preamble.
129
139
let _ = builder. preamble ( variant) ;
@@ -148,6 +158,7 @@ impl<'a> LintDiagnosticDerive<'a> {
148
158
DiagnosticDeriveError :: ErrorHandled . to_compile_error ( )
149
159
}
150
160
Some ( slug) => {
161
+ slugs. borrow_mut ( ) . push ( slug. clone ( ) ) ;
151
162
quote ! {
152
163
crate :: fluent_generated:: #slug. into( )
153
164
}
@@ -156,7 +167,7 @@ impl<'a> LintDiagnosticDerive<'a> {
156
167
} ) ;
157
168
158
169
let diag = & builder. diag ;
159
- structure. gen_impl ( quote ! {
170
+ let mut imp = structure. gen_impl ( quote ! {
160
171
gen impl <' __a> rustc_errors:: DecorateLint <' __a, ( ) > for @Self {
161
172
#[ track_caller]
162
173
fn decorate_lint<' __b>(
@@ -171,7 +182,12 @@ impl<'a> LintDiagnosticDerive<'a> {
171
182
#msg
172
183
}
173
184
}
174
- } )
185
+ } ) ;
186
+ for test in slugs. borrow ( ) . iter ( ) . map ( |s| generate_test ( s, & structure) ) {
187
+ imp. extend ( test) ;
188
+ }
189
+
190
+ imp
175
191
}
176
192
}
177
193
@@ -198,3 +214,40 @@ impl Mismatch {
198
214
}
199
215
}
200
216
}
217
+
218
+ /// Generates a `#[test]` that verifies that all referenced variables
219
+ /// exist on this structure.
220
+ fn generate_test ( slug : & syn:: Path , structure : & Structure < ' _ > ) -> TokenStream {
221
+ // FIXME: We can't identify variables in a subdiagnostic
222
+ for field in structure. variants ( ) . iter ( ) . flat_map ( |v| v. ast ( ) . fields . iter ( ) ) {
223
+ for attr_name in field. attrs . iter ( ) . filter_map ( |at| at. path ( ) . get_ident ( ) ) {
224
+ if attr_name == "subdiagnostic" {
225
+ return quote ! ( ) ;
226
+ }
227
+ }
228
+ }
229
+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
230
+ // We need to make sure that the same diagnostic slug can be used multiple times without causing an
231
+ // error, so just have a global counter here.
232
+ static COUNTER : AtomicUsize = AtomicUsize :: new ( 0 ) ;
233
+ let slug = slug. get_ident ( ) . unwrap ( ) ;
234
+ let ident = quote:: format_ident!( "verify_{slug}_{}" , COUNTER . fetch_add( 1 , Ordering :: Relaxed ) ) ;
235
+ let ref_slug = quote:: format_ident!( "{slug}_refs" ) ;
236
+ let struct_name = & structure. ast ( ) . ident ;
237
+ let variables: Vec < _ > = structure
238
+ . variants ( )
239
+ . iter ( )
240
+ . flat_map ( |v| v. ast ( ) . fields . iter ( ) . filter_map ( |f| f. ident . as_ref ( ) . map ( |i| i. to_string ( ) ) ) )
241
+ . collect ( ) ;
242
+ // tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this
243
+ quote ! {
244
+ #[ cfg( test) ]
245
+ #[ test ]
246
+ fn #ident( ) {
247
+ let variables = [ #( #variables) , * ] ;
248
+ for vref in crate :: fluent_generated:: #ref_slug {
249
+ assert!( variables. contains( vref) , "{}: variable `{vref}` not found ({})" , stringify!( #struct_name) , stringify!( #slug) ) ;
250
+ }
251
+ }
252
+ }
253
+ }
0 commit comments