about summary refs log tree commit diff
path: root/compiler/rustc_macros/src
diff options
context:
space:
mode:
authorclubby789 <jamie@hill-daniel.co.uk>2023-05-05 21:26:42 +0100
committerclubby789 <jamie@hill-daniel.co.uk>2023-05-26 10:41:36 +0000
commit9b5574f028c75d794b51c96ff6471ddf756f329b (patch)
treed57f45ebda8ac979c9e83b43963a0877cec544b6 /compiler/rustc_macros/src
parentbe72f2587c91579406117f99fa332383d66b7dcd (diff)
downloadrust-9b5574f028c75d794b51c96ff6471ddf756f329b.tar.gz
rust-9b5574f028c75d794b51c96ff6471ddf756f329b.zip
Validate fluent variable references with `debug_assertions`
Diffstat (limited to 'compiler/rustc_macros/src')
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs68
1 files changed, 64 insertions, 4 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index 12a954258d1..b9eba65f66a 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -1,5 +1,7 @@
 #![deny(unused_must_use)]
 
+use std::cell::RefCell;
+
 use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind};
 use crate::diagnostics::error::{span_err, DiagnosticDeriveError};
 use crate::diagnostics::utils::SetOnce;
@@ -28,6 +30,7 @@ impl<'a> DiagnosticDerive<'a> {
     pub(crate) fn into_tokens(self) -> TokenStream {
         let DiagnosticDerive { mut structure, mut builder } = self;
 
+        let slugs = RefCell::new(Vec::new());
         let implementation = builder.each_variant(&mut structure, |mut builder, variant| {
             let preamble = builder.preamble(variant);
             let body = builder.body(variant);
@@ -56,6 +59,7 @@ impl<'a> DiagnosticDerive<'a> {
                     return DiagnosticDeriveError::ErrorHandled.to_compile_error();
                 }
                 Some(slug) => {
+                    slugs.borrow_mut().push(slug.clone());
                     quote! {
                         let mut #diag = #handler.struct_diagnostic(crate::fluent_generated::#slug);
                     }
@@ -73,7 +77,8 @@ impl<'a> DiagnosticDerive<'a> {
         });
 
         let DiagnosticDeriveKind::Diagnostic { handler } = &builder.kind else { unreachable!() };
-        structure.gen_impl(quote! {
+        #[allow(unused_mut)]
+        let mut imp = structure.gen_impl(quote! {
             gen impl<'__diagnostic_handler_sess, G>
                     rustc_errors::IntoDiagnostic<'__diagnostic_handler_sess, G>
                     for @Self
@@ -89,7 +94,14 @@ impl<'a> DiagnosticDerive<'a> {
                     #implementation
                 }
             }
-        })
+        });
+        #[cfg(debug_assertions)]
+        {
+            for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
+                imp.extend(test);
+            }
+        }
+        imp
     }
 }
 
@@ -124,6 +136,7 @@ impl<'a> LintDiagnosticDerive<'a> {
             }
         });
 
+        let slugs = RefCell::new(Vec::new());
         let msg = builder.each_variant(&mut structure, |mut builder, variant| {
             // Collect the slug by generating the preamble.
             let _ = builder.preamble(variant);
@@ -148,6 +161,7 @@ impl<'a> LintDiagnosticDerive<'a> {
                     DiagnosticDeriveError::ErrorHandled.to_compile_error()
                 }
                 Some(slug) => {
+                    slugs.borrow_mut().push(slug.clone());
                     quote! {
                         crate::fluent_generated::#slug.into()
                     }
@@ -156,7 +170,8 @@ impl<'a> LintDiagnosticDerive<'a> {
         });
 
         let diag = &builder.diag;
-        structure.gen_impl(quote! {
+        #[allow(unused_mut)]
+        let mut imp = structure.gen_impl(quote! {
             gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
                 #[track_caller]
                 fn decorate_lint<'__b>(
@@ -171,7 +186,14 @@ impl<'a> LintDiagnosticDerive<'a> {
                     #msg
                 }
             }
-        })
+        });
+        #[cfg(debug_assertions)]
+        {
+            for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
+                imp.extend(test);
+            }
+        }
+        imp
     }
 }
 
@@ -198,3 +220,41 @@ impl Mismatch {
         }
     }
 }
+
+/// Generates a `#[test]` that verifies that all referenced variables
+/// exist on this structure.
+#[cfg(debug_assertions)]
+fn generate_test(slug: &syn::Path, structure: &Structure<'_>) -> TokenStream {
+    // FIXME: We can't identify variables in a subdiagnostic
+    for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) {
+        for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) {
+            if attr_name == "subdiagnostic" {
+                return quote!();
+            }
+        }
+    }
+    use std::sync::atomic::{AtomicUsize, Ordering};
+    // We need to make sure that the same diagnostic slug can be used multiple times without causing an
+    // error, so just have a global counter here.
+    static COUNTER: AtomicUsize = AtomicUsize::new(0);
+    let slug = slug.get_ident().unwrap();
+    let ident = quote::format_ident!("verify_{slug}_{}", COUNTER.fetch_add(1, Ordering::Relaxed));
+    let ref_slug = quote::format_ident!("{slug}_refs");
+    let struct_name = &structure.ast().ident;
+    let variables: Vec<_> = structure
+        .variants()
+        .iter()
+        .flat_map(|v| v.ast().fields.iter().filter_map(|f| f.ident.as_ref().map(|i| i.to_string())))
+        .collect();
+    // tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this
+    quote! {
+        #[cfg(test)]
+        #[test ]
+        fn #ident() {
+            let variables = [#(#variables),*];
+            for vref in crate::fluent_generated::#ref_slug {
+                assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug));
+            }
+        }
+    }
+}