diff options
Diffstat (limited to 'compiler/rustc_macros/src')
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/diagnostic.rs | 61 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs | 14 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/error.rs | 10 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/subdiagnostic.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/diagnostics/utils.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_macros/src/newtype.rs | 6 |
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"); }; |
