11#![ deny( unused_must_use) ]
22
3+ use std:: cell:: RefCell ;
4+
35use crate :: diagnostics:: diagnostic_builder:: { DiagnosticDeriveBuilder , DiagnosticDeriveKind } ;
46use crate :: diagnostics:: error:: { span_err, DiagnosticDeriveError } ;
57use crate :: diagnostics:: utils:: SetOnce ;
@@ -28,6 +30,7 @@ impl<'a> DiagnosticDerive<'a> {
2830 pub ( crate ) fn into_tokens ( self ) -> TokenStream {
2931 let DiagnosticDerive { mut structure, mut builder } = self ;
3032
33+ let slugs = RefCell :: new ( Vec :: new ( ) ) ;
3134 let implementation = builder. each_variant ( & mut structure, |mut builder, variant| {
3235 let preamble = builder. preamble ( variant) ;
3336 let body = builder. body ( variant) ;
@@ -56,6 +59,7 @@ impl<'a> DiagnosticDerive<'a> {
5659 return DiagnosticDeriveError :: ErrorHandled . to_compile_error ( ) ;
5760 }
5861 Some ( slug) => {
62+ slugs. borrow_mut ( ) . push ( slug. clone ( ) ) ;
5963 quote ! {
6064 let mut #diag = #handler. struct_diagnostic( crate :: fluent_generated:: #slug) ;
6165 }
@@ -73,7 +77,8 @@ impl<'a> DiagnosticDerive<'a> {
7377 } ) ;
7478
7579 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 ! {
7782 gen impl <' __diagnostic_handler_sess, G >
7883 rustc_errors:: IntoDiagnostic <' __diagnostic_handler_sess, G >
7984 for @Self
@@ -89,7 +94,14 @@ impl<'a> DiagnosticDerive<'a> {
8994 #implementation
9095 }
9196 }
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
93105 }
94106}
95107
@@ -124,6 +136,7 @@ impl<'a> LintDiagnosticDerive<'a> {
124136 }
125137 } ) ;
126138
139+ let slugs = RefCell :: new ( Vec :: new ( ) ) ;
127140 let msg = builder. each_variant ( & mut structure, |mut builder, variant| {
128141 // Collect the slug by generating the preamble.
129142 let _ = builder. preamble ( variant) ;
@@ -148,6 +161,7 @@ impl<'a> LintDiagnosticDerive<'a> {
148161 DiagnosticDeriveError :: ErrorHandled . to_compile_error ( )
149162 }
150163 Some ( slug) => {
164+ slugs. borrow_mut ( ) . push ( slug. clone ( ) ) ;
151165 quote ! {
152166 crate :: fluent_generated:: #slug. into( )
153167 }
@@ -156,7 +170,8 @@ impl<'a> LintDiagnosticDerive<'a> {
156170 } ) ;
157171
158172 let diag = & builder. diag ;
159- structure. gen_impl ( quote ! {
173+ #[ allow( unused_mut) ]
174+ let mut imp = structure. gen_impl ( quote ! {
160175 gen impl <' __a> rustc_errors:: DecorateLint <' __a, ( ) > for @Self {
161176 #[ track_caller]
162177 fn decorate_lint<' __b>(
@@ -171,7 +186,14 @@ impl<'a> LintDiagnosticDerive<'a> {
171186 #msg
172187 }
173188 }
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
175197 }
176198}
177199
@@ -198,3 +220,41 @@ impl Mismatch {
198220 }
199221 }
200222}
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