blob: 7e784e3464e9f1805cdc46d6bed2519e47af3dc7 [file] [log] [blame] [edit]
#![deny(unused_must_use)]
use std::cell::RefCell;
use proc_macro2::TokenStream;
use quote::quote;
use synstructure::Structure;
use crate::diagnostics::diagnostic_builder::DiagnosticDeriveKind;
use crate::diagnostics::error::DiagnosticDeriveError;
/// The central struct for constructing the `into_diag` method from an annotated struct.
pub(crate) struct DiagnosticDerive<'a> {
structure: Structure<'a>,
}
impl<'a> DiagnosticDerive<'a> {
pub(crate) fn new(structure: Structure<'a>) -> Self {
Self { structure }
}
pub(crate) fn into_tokens(self) -> TokenStream {
let DiagnosticDerive { mut structure } = self;
let kind = DiagnosticDeriveKind::Diagnostic;
let slugs = RefCell::new(Vec::new());
let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
let preamble = builder.preamble(variant);
let body = builder.body(variant);
let Some(slug) = builder.primary_message() else {
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
};
slugs.borrow_mut().push(slug.clone());
let init = quote! {
let mut diag = rustc_errors::Diag::new(
dcx,
level,
crate::fluent_generated::#slug
);
};
let formatting_init = &builder.formatting_init;
quote! {
#init
#formatting_init
#preamble
#body
diag
}
});
// A lifetime of `'a` causes conflicts, but `_sess` is fine.
// FIXME(edition_2024): Fix the `keyword_idents_2024` lint to not trigger here?
#[allow(keyword_idents_2024)]
let mut imp = structure.gen_impl(quote! {
gen impl<'_sess, G> rustc_errors::Diagnostic<'_sess, G> for @Self
where G: rustc_errors::EmissionGuarantee
{
#[track_caller]
fn into_diag(
self,
dcx: rustc_errors::DiagCtxtHandle<'_sess>,
level: rustc_errors::Level
) -> rustc_errors::Diag<'_sess, G> {
#implementation
}
}
});
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
imp.extend(test);
}
imp
}
}
/// The central struct for constructing the `decorate_lint` method from an annotated struct.
pub(crate) struct LintDiagnosticDerive<'a> {
structure: Structure<'a>,
}
impl<'a> LintDiagnosticDerive<'a> {
pub(crate) fn new(structure: Structure<'a>) -> Self {
Self { structure }
}
pub(crate) fn into_tokens(self) -> TokenStream {
let LintDiagnosticDerive { mut structure } = self;
let kind = DiagnosticDeriveKind::LintDiagnostic;
let slugs = RefCell::new(Vec::new());
let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
let preamble = builder.preamble(variant);
let body = builder.body(variant);
let Some(slug) = builder.primary_message() else {
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
};
slugs.borrow_mut().push(slug.clone());
let primary_message = quote! {
diag.primary_message(crate::fluent_generated::#slug);
};
let formatting_init = &builder.formatting_init;
quote! {
#primary_message
#preamble
#formatting_init
#body
diag
}
});
// FIXME(edition_2024): Fix the `keyword_idents_2024` lint to not trigger here?
#[allow(keyword_idents_2024)]
let mut imp = structure.gen_impl(quote! {
gen impl<'__a> rustc_errors::LintDiagnostic<'__a, ()> for @Self {
#[track_caller]
fn decorate_lint<'__b>(
self,
diag: &'__b mut rustc_errors::Diag<'__a, ()>
) {
#implementation;
}
}
});
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
imp.extend(test);
}
imp
}
}
/// 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));
}
}
}
}