233 lines
8.8 KiB
Rust
233 lines
8.8 KiB
Rust
#![deny(unused_must_use)]
|
|
|
|
use std::cell::RefCell;
|
|
|
|
use crate::diagnostics::diagnostic_builder::DiagnosticDeriveKind;
|
|
use crate::diagnostics::error::{span_err, DiagnosticDeriveError};
|
|
use crate::diagnostics::utils::SetOnce;
|
|
use proc_macro2::TokenStream;
|
|
use quote::quote;
|
|
use syn::spanned::Spanned;
|
|
use synstructure::Structure;
|
|
|
|
/// 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 init = match builder.slug.value_ref() {
|
|
None => {
|
|
span_err(builder.span, "diagnostic slug not specified")
|
|
.help(
|
|
"specify the slug as the first argument to the `#[diag(...)]` \
|
|
attribute, such as `#[diag(hir_analysis_example_error)]`",
|
|
)
|
|
.emit();
|
|
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
|
|
}
|
|
Some(slug)
|
|
if let Some(Mismatch { slug_name, crate_name, slug_prefix }) =
|
|
Mismatch::check(slug) =>
|
|
{
|
|
span_err(slug.span().unwrap(), "diagnostic slug and crate name do not match")
|
|
.note(format!("slug is `{slug_name}` but the crate name is `{crate_name}`"))
|
|
.help(format!("expected a slug starting with `{slug_prefix}_...`"))
|
|
.emit();
|
|
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
|
|
}
|
|
Some(slug) => {
|
|
slugs.borrow_mut().push(slug.clone());
|
|
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 primary_message = match builder.slug.value_ref() {
|
|
None => {
|
|
span_err(builder.span, "diagnostic slug not specified")
|
|
.help(
|
|
"specify the slug as the first argument to the attribute, such as \
|
|
`#[diag(compiletest_example)]`",
|
|
)
|
|
.emit();
|
|
DiagnosticDeriveError::ErrorHandled.to_compile_error()
|
|
}
|
|
Some(slug)
|
|
if let Some(Mismatch { slug_name, crate_name, slug_prefix }) =
|
|
Mismatch::check(slug) =>
|
|
{
|
|
span_err(slug.span().unwrap(), "diagnostic slug and crate name do not match")
|
|
.note(format!("slug is `{slug_name}` but the crate name is `{crate_name}`"))
|
|
.help(format!("expected a slug starting with `{slug_prefix}_...`"))
|
|
.emit();
|
|
DiagnosticDeriveError::ErrorHandled.to_compile_error()
|
|
}
|
|
Some(slug) => {
|
|
slugs.borrow_mut().push(slug.clone());
|
|
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
|
|
}
|
|
}
|
|
|
|
struct Mismatch {
|
|
slug_name: String,
|
|
crate_name: String,
|
|
slug_prefix: String,
|
|
}
|
|
|
|
impl Mismatch {
|
|
/// Checks whether the slug starts with the crate name it's in.
|
|
fn check(slug: &syn::Path) -> Option<Mismatch> {
|
|
// If this is missing we're probably in a test, so bail.
|
|
let crate_name = std::env::var("CARGO_CRATE_NAME").ok()?;
|
|
|
|
// If we're not in a "rustc_" crate, bail.
|
|
let Some(("rustc", slug_prefix)) = crate_name.split_once('_') else { return None };
|
|
|
|
let slug_name = slug.segments.first()?.ident.to_string();
|
|
if !slug_name.starts_with(slug_prefix) {
|
|
Some(Mismatch { slug_name, slug_prefix: slug_prefix.to_string(), crate_name })
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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));
|
|
}
|
|
}
|
|
}
|
|
}
|