about summary refs log tree commit diff
path: root/compiler/rustc_macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_macros/src')
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs61
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs14
-rw-r--r--compiler/rustc_macros/src/diagnostics/error.rs10
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs6
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs2
-rw-r--r--compiler/rustc_macros/src/newtype.rs6
6 files changed, 80 insertions, 19 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index 12a954258d1..04b7c5feebe 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! {
+
+        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,11 @@ impl<'a> DiagnosticDerive<'a> {
                     #implementation
                 }
             }
-        })
+        });
+        for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
+            imp.extend(test);
+        }
+        imp
     }
 }
 
@@ -124,6 +133,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 +158,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 +167,7 @@ impl<'a> LintDiagnosticDerive<'a> {
         });
 
         let diag = &builder.diag;
-        structure.gen_impl(quote! {
+        let mut imp = structure.gen_impl(quote! {
             gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
                 #[track_caller]
                 fn decorate_lint<'__b>(
@@ -171,7 +182,12 @@ impl<'a> LintDiagnosticDerive<'a> {
                     #msg
                 }
             }
-        })
+        });
+        for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
+            imp.extend(test);
+        }
+
+        imp
     }
 }
 
@@ -198,3 +214,40 @@ impl Mismatch {
         }
     }
 }
+
+/// Generates a `#[test]` that verifies that all referenced variables
+/// exist on this structure.
+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));
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index 2e6e84ad80e..e9a5cd9de97 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -203,14 +203,18 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
                 if first && (nested.input.is_empty() || nested.input.peek(Token![,])) {
                     self.slug.set_once(path.clone(), path.span().unwrap());
                     first = false;
-                    return Ok(())
+                    return Ok(());
                 }
 
                 first = false;
 
                 let Ok(nested) = nested.value() else {
-                    span_err(nested.input.span().unwrap(), "diagnostic slug must be the first argument").emit();
-                    return Ok(())
+                    span_err(
+                        nested.input.span().unwrap(),
+                        "diagnostic slug must be the first argument",
+                    )
+                    .emit();
+                    return Ok(());
                 };
 
                 if path.is_ident("code") {
@@ -221,7 +225,9 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
                         #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
                     });
                 } else {
-                    span_err(path.span().unwrap(), "unknown argument").note("only the `code` parameter is valid after the slug").emit();
+                    span_err(path.span().unwrap(), "unknown argument")
+                        .note("only the `code` parameter is valid after the slug")
+                        .emit();
 
                     // consume the buffer so we don't have syntax errors from syn
                     let _ = nested.parse::<TokenStream>();
diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs
index b37dc826d28..84b18a62028 100644
--- a/compiler/rustc_macros/src/diagnostics/error.rs
+++ b/compiler/rustc_macros/src/diagnostics/error.rs
@@ -54,7 +54,7 @@ fn path_to_string(path: &syn::Path) -> String {
 
 /// Returns an error diagnostic on span `span` with msg `msg`.
 #[must_use]
-pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
+pub(crate) fn span_err<T: Into<String>>(span: impl MultiSpan, msg: T) -> Diagnostic {
     Diagnostic::spanned(span, Level::Error, msg)
 }
 
@@ -77,11 +77,9 @@ pub(crate) fn invalid_attr(attr: &Attribute) -> Diagnostic {
     let span = attr.span().unwrap();
     let path = path_to_string(attr.path());
     match attr.meta {
-        Meta::Path(_) => span_err(span, &format!("`#[{path}]` is not a valid attribute")),
-        Meta::NameValue(_) => {
-            span_err(span, &format!("`#[{path} = ...]` is not a valid attribute"))
-        }
-        Meta::List(_) => span_err(span, &format!("`#[{path}(...)]` is not a valid attribute")),
+        Meta::Path(_) => span_err(span, format!("`#[{path}]` is not a valid attribute")),
+        Meta::NameValue(_) => span_err(span, format!("`#[{path} = ...]` is not a valid attribute")),
+        Meta::List(_) => span_err(span, format!("`#[{path}(...)]` is not a valid attribute")),
     }
 }
 
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index e3d9eb96574..877e9745054 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -188,7 +188,9 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
         let mut kind_slugs = vec![];
 
         for attr in self.variant.ast().attrs {
-            let Some(SubdiagnosticVariant { kind, slug, no_span }) = SubdiagnosticVariant::from_attr(attr, self)? else {
+            let Some(SubdiagnosticVariant { kind, slug, no_span }) =
+                SubdiagnosticVariant::from_attr(attr, self)?
+            else {
                 // Some attributes aren't errors - like documentation comments - but also aren't
                 // subdiagnostics.
                 continue;
@@ -200,7 +202,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
 
                 throw_span_err!(
                     attr.span().unwrap(),
-                    &format!(
+                    format!(
                         "diagnostic slug must be first argument of a `#[{name}(...)]` attribute"
                     )
                 );
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index 85dd9f6a3ce..12563292181 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -347,7 +347,7 @@ pub(crate) trait HasFieldMap {
                 None => {
                     span_err(
                         span.unwrap(),
-                        &format!("`{field}` doesn't refer to a field on this type"),
+                        format!("`{field}` doesn't refer to a field on this type"),
                     )
                     .emit();
                     quote! {
diff --git a/compiler/rustc_macros/src/newtype.rs b/compiler/rustc_macros/src/newtype.rs
index 415a89b0f92..72b47de1abc 100644
--- a/compiler/rustc_macros/src/newtype.rs
+++ b/compiler/rustc_macros/src/newtype.rs
@@ -36,7 +36,8 @@ impl Parse for Newtype {
                     false
                 }
                 "max" => {
-                    let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta else {
+                    let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta
+                    else {
                         panic!("#[max = NUMBER] attribute requires max value");
                     };
 
@@ -47,7 +48,8 @@ impl Parse for Newtype {
                     false
                 }
                 "debug_format" => {
-                    let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta else {
+                    let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta
+                    else {
                         panic!("#[debug_format = FMT] attribute requires a format");
                     };