summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_macros/src/diagnostics/diagnostic.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_macros/src/diagnostics/diagnostic.rs')
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs61
1 files changed, 57 insertions, 4 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index 12a954258..04b7c5fee 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));
+ }
+ }
+ }
+}