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+
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,11 @@ impl<'a> DiagnosticDerive<'a> {
8994 #implementation
9095 }
9196 }
92- } )
97+ } ) ;
98+ for test in slugs. borrow ( ) . iter ( ) . map ( |s| generate_test ( s, & structure) ) {
99+ imp. extend ( test) ;
100+ }
101+ imp
93102 }
94103}
95104
@@ -124,6 +133,7 @@ impl<'a> LintDiagnosticDerive<'a> {
124133 }
125134 } ) ;
126135
136+ let slugs = RefCell :: new ( Vec :: new ( ) ) ;
127137 let msg = builder. each_variant ( & mut structure, |mut builder, variant| {
128138 // Collect the slug by generating the preamble.
129139 let _ = builder. preamble ( variant) ;
@@ -148,6 +158,7 @@ impl<'a> LintDiagnosticDerive<'a> {
148158 DiagnosticDeriveError :: ErrorHandled . to_compile_error ( )
149159 }
150160 Some ( slug) => {
161+ slugs. borrow_mut ( ) . push ( slug. clone ( ) ) ;
151162 quote ! {
152163 crate :: fluent_generated:: #slug. into( )
153164 }
@@ -156,7 +167,7 @@ impl<'a> LintDiagnosticDerive<'a> {
156167 } ) ;
157168
158169 let diag = & builder. diag ;
159- structure. gen_impl ( quote ! {
170+ let mut imp = structure. gen_impl ( quote ! {
160171 gen impl <' __a> rustc_errors:: DecorateLint <' __a, ( ) > for @Self {
161172 #[ track_caller]
162173 fn decorate_lint<' __b>(
@@ -171,7 +182,12 @@ impl<'a> LintDiagnosticDerive<'a> {
171182 #msg
172183 }
173184 }
174- } )
185+ } ) ;
186+ for test in slugs. borrow ( ) . iter ( ) . map ( |s| generate_test ( s, & structure) ) {
187+ imp. extend ( test) ;
188+ }
189+
190+ imp
175191 }
176192}
177193
@@ -198,3 +214,40 @@ impl Mismatch {
198214 }
199215 }
200216}
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