Validate fluent variable references with debug_assertions
This commit is contained in:
parent
be72f2587c
commit
9b5574f028
@ -179,7 +179,8 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
|
||||
let mut previous_defns = HashMap::new();
|
||||
let mut message_refs = Vec::new();
|
||||
for entry in resource.entries() {
|
||||
if let Entry::Message(Message { id: Identifier { name }, attributes, value, .. }) = entry {
|
||||
if let Entry::Message(msg) = entry {
|
||||
let Message { id: Identifier { name }, attributes, value, .. } = msg;
|
||||
let _ = previous_defns.entry(name.to_string()).or_insert(resource_span);
|
||||
if name.contains('-') {
|
||||
Diagnostic::spanned(
|
||||
@ -229,9 +230,10 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
|
||||
continue;
|
||||
}
|
||||
|
||||
let msg = format!("Constant referring to Fluent message `{name}` from `{crate_name}`");
|
||||
let docstr =
|
||||
format!("Constant referring to Fluent message `{name}` from `{crate_name}`");
|
||||
constants.extend(quote! {
|
||||
#[doc = #msg]
|
||||
#[doc = #docstr]
|
||||
pub const #snake_name: crate::DiagnosticMessage =
|
||||
crate::DiagnosticMessage::FluentIdentifier(
|
||||
std::borrow::Cow::Borrowed(#name),
|
||||
@ -269,6 +271,17 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
|
||||
);
|
||||
});
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// Record variables referenced by these messages so we can produce
|
||||
// tests in the derive diagnostics to validate them.
|
||||
let ident = quote::format_ident!("{snake_name}_refs");
|
||||
let vrefs = variable_references(msg);
|
||||
constants.extend(quote! {
|
||||
#[cfg(test)]
|
||||
pub const #ident: &[&str] = &[#(#vrefs),*];
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,3 +347,29 @@ pub mod _subdiag {
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn variable_references<'a>(msg: &Message<&'a str>) -> Vec<&'a str> {
|
||||
let mut refs = vec![];
|
||||
if let Some(Pattern { elements }) = &msg.value {
|
||||
for elt in elements {
|
||||
if let PatternElement::Placeable {
|
||||
expression: Expression::Inline(InlineExpression::VariableReference { id }),
|
||||
} = elt
|
||||
{
|
||||
refs.push(id.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
for attr in &msg.attributes {
|
||||
for elt in &attr.value.elements {
|
||||
if let PatternElement::Placeable {
|
||||
expression: Expression::Inline(InlineExpression::VariableReference { id }),
|
||||
} = elt
|
||||
{
|
||||
refs.push(id.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
refs
|
||||
}
|
||||
|
@ -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 @@ pub(crate) fn new(diag: syn::Ident, handler: syn::Ident, structure: Structure<'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 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
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 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
});
|
||||
|
||||
let DiagnosticDeriveKind::Diagnostic { handler } = &builder.kind else { unreachable!() };
|
||||
structure.gen_impl(quote! {
|
||||
#[allow(unused_mut)]
|
||||
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,14 @@ fn into_diagnostic(
|
||||
#implementation
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
|
||||
imp.extend(test);
|
||||
}
|
||||
}
|
||||
imp
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +136,7 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
}
|
||||
});
|
||||
|
||||
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 +161,7 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
DiagnosticDeriveError::ErrorHandled.to_compile_error()
|
||||
}
|
||||
Some(slug) => {
|
||||
slugs.borrow_mut().push(slug.clone());
|
||||
quote! {
|
||||
crate::fluent_generated::#slug.into()
|
||||
}
|
||||
@ -156,7 +170,8 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
});
|
||||
|
||||
let diag = &builder.diag;
|
||||
structure.gen_impl(quote! {
|
||||
#[allow(unused_mut)]
|
||||
let mut imp = structure.gen_impl(quote! {
|
||||
gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
|
||||
#[track_caller]
|
||||
fn decorate_lint<'__b>(
|
||||
@ -171,7 +186,14 @@ fn msg(&self) -> rustc_errors::DiagnosticMessage {
|
||||
#msg
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
|
||||
imp.extend(test);
|
||||
}
|
||||
}
|
||||
imp
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,3 +220,41 @@ fn check(slug: &syn::Path) -> Option<Mismatch> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a `#[test]` that verifies that all referenced variables
|
||||
/// exist on this structure.
|
||||
#[cfg(debug_assertions)]
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user