about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-06-28 03:47:02 +0000
committerbors <bors@rust-lang.org>2023-06-28 03:47:02 +0000
commit08fd6f719ee764cf62659ddf481e22dbfe4b8894 (patch)
treec1f5435c0675b007965cd1fac6aa4d275493057a
parentbb95b7dcd6a247df0f4a6762a673a2910a81de9c (diff)
parent8969d974377b17861970d161a46a0fc80e3d34ad (diff)
downloadrust-08fd6f719ee764cf62659ddf481e22dbfe4b8894.tar.gz
rust-08fd6f719ee764cf62659ddf481e22dbfe4b8894.zip
Auto merge of #111269 - clubby789:validate-fluent-variables, r=davidtwco
Validate fluent variable references in tests

Closes #101109

Under `cfg(test)`, the `fluent_messages` macro will emit a list of variables referenced by each message and its attributes. The derive attribute will now emit a `#[test]` that checks that each referenced variable exists in the structure it's applied to.
-rw-r--r--compiler/rustc_fluent_macro/src/fluent.rs42
-rw-r--r--compiler/rustc_hir_analysis/messages.ftl2
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs61
-rw-r--r--compiler/rustc_passes/src/errors.rs8
-rw-r--r--tests/ui-fulldeps/session-diagnostic/example.ftl2
-rw-r--r--tests/ui-fulldeps/session-diagnostic/invalid-variable.rs21
-rw-r--r--tests/ui/stability-attribute/auxiliary/default_body.rs4
-rw-r--r--tests/ui/stability-attribute/default-body-stability-err.rs1
-rw-r--r--tests/ui/stability-attribute/default-body-stability-err.stderr14
-rw-r--r--tests/ui/stability-attribute/default-body-stability-ok-impls.rs2
10 files changed, 139 insertions, 18 deletions
diff --git a/compiler/rustc_fluent_macro/src/fluent.rs b/compiler/rustc_fluent_macro/src/fluent.rs
index 9dffc9a7645..56e23ac2775 100644
--- a/compiler/rustc_fluent_macro/src/fluent.rs
+++ b/compiler/rustc_fluent_macro/src/fluent.rs
@@ -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,15 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
                         );
                 });
             }
+
+            // 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 +345,28 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
     }
     .into()
 }
+
+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
+}
diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl
index cd6cf36baa4..edc6bcacc68 100644
--- a/compiler/rustc_hir_analysis/messages.ftl
+++ b/compiler/rustc_hir_analysis/messages.ftl
@@ -137,7 +137,7 @@ hir_analysis_missing_trait_item_suggestion = implement the missing item: `{$snip
 
 hir_analysis_missing_trait_item_unstable = not all trait items implemented, missing: `{$missing_item_name}`
     .note = default implementation of `{$missing_item_name}` is unstable
-    .some_note = use of unstable library feature '{$feature}': {$r}
+    .some_note = use of unstable library feature '{$feature}': {$reason}
     .none_note = use of unstable library feature '{$feature}'
 
 hir_analysis_missing_type_params =
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_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 7890c93d5ff..3fe7feb9dfa 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1150,14 +1150,6 @@ pub struct UnixSigpipeValues {
     pub span: Span,
 }
 
-#[derive(Diagnostic)]
-#[diag(passes_no_main_function, code = "E0601")]
-pub struct NoMainFunction {
-    #[primary_span]
-    pub span: Span,
-    pub crate_name: String,
-}
-
 pub struct NoMainErr {
     pub sp: Span,
     pub crate_name: Symbol,
diff --git a/tests/ui-fulldeps/session-diagnostic/example.ftl b/tests/ui-fulldeps/session-diagnostic/example.ftl
index cb2d476d815..1d1ba8e1bd5 100644
--- a/tests/ui-fulldeps/session-diagnostic/example.ftl
+++ b/tests/ui-fulldeps/session-diagnostic/example.ftl
@@ -3,3 +3,5 @@ no_crate_example = this is an example message used in testing
     .help = with a help
     .suggestion = with a suggestion
     .label = with a label
+
+no_crate_bad_reference = {$r} does not exist
diff --git a/tests/ui-fulldeps/session-diagnostic/invalid-variable.rs b/tests/ui-fulldeps/session-diagnostic/invalid-variable.rs
new file mode 100644
index 00000000000..57798dda3eb
--- /dev/null
+++ b/tests/ui-fulldeps/session-diagnostic/invalid-variable.rs
@@ -0,0 +1,21 @@
+// run-fail
+// compile-flags: --test
+// test that messages referencing non-existent fields cause test failures
+
+#![feature(rustc_private)]
+#![crate_type = "lib"]
+
+extern crate rustc_driver;
+extern crate rustc_fluent_macro;
+extern crate rustc_macros;
+extern crate rustc_errors;
+use rustc_fluent_macro::fluent_messages;
+use rustc_macros::Diagnostic;
+use rustc_errors::{SubdiagnosticMessage, DiagnosticMessage};
+extern crate rustc_session;
+
+fluent_messages! { "./example.ftl" }
+
+#[derive(Diagnostic)]
+#[diag(no_crate_bad_reference)]
+struct BadRef;
diff --git a/tests/ui/stability-attribute/auxiliary/default_body.rs b/tests/ui/stability-attribute/auxiliary/default_body.rs
index 3a177419d66..2f315eb1bc8 100644
--- a/tests/ui/stability-attribute/auxiliary/default_body.rs
+++ b/tests/ui/stability-attribute/auxiliary/default_body.rs
@@ -11,6 +11,10 @@ pub trait JustTrait {
     #[rustc_default_body_unstable(feature = "fun_default_body", issue = "none")]
     #[stable(feature = "stable_feature", since = "1.0.0")]
     fn fun() {}
+
+    #[rustc_default_body_unstable(feature = "fun_default_body", issue = "none", reason = "reason")]
+    #[stable(feature = "stable_feature", since = "1.0.0")]
+    fn fun2() {}
 }
 
 #[rustc_must_implement_one_of(eq, neq)]
diff --git a/tests/ui/stability-attribute/default-body-stability-err.rs b/tests/ui/stability-attribute/default-body-stability-err.rs
index ecb281bccf6..d1a3597687d 100644
--- a/tests/ui/stability-attribute/default-body-stability-err.rs
+++ b/tests/ui/stability-attribute/default-body-stability-err.rs
@@ -10,6 +10,7 @@ struct Type;
 impl JustTrait for Type {}
 //~^ ERROR not all trait items implemented, missing: `CONSTANT` [E0046]
 //~| ERROR not all trait items implemented, missing: `fun` [E0046]
+//~| ERROR not all trait items implemented, missing: `fun2` [E0046]
 
 impl Equal for Type {
     //~^ ERROR not all trait items implemented, missing: `eq` [E0046]
diff --git a/tests/ui/stability-attribute/default-body-stability-err.stderr b/tests/ui/stability-attribute/default-body-stability-err.stderr
index ef666f30fc2..12ec9ea3adb 100644
--- a/tests/ui/stability-attribute/default-body-stability-err.stderr
+++ b/tests/ui/stability-attribute/default-body-stability-err.stderr
@@ -18,8 +18,18 @@ LL | impl JustTrait for Type {}
    = note: use of unstable library feature 'fun_default_body'
    = help: add `#![feature(fun_default_body)]` to the crate attributes to enable
 
+error[E0046]: not all trait items implemented, missing: `fun2`
+  --> $DIR/default-body-stability-err.rs:10:1
+   |
+LL | impl JustTrait for Type {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: default implementation of `fun2` is unstable
+   = note: use of unstable library feature 'fun_default_body': reason
+   = help: add `#![feature(fun_default_body)]` to the crate attributes to enable
+
 error[E0046]: not all trait items implemented, missing: `eq`
-  --> $DIR/default-body-stability-err.rs:14:1
+  --> $DIR/default-body-stability-err.rs:15:1
    |
 LL | / impl Equal for Type {
 LL | |
@@ -33,6 +43,6 @@ LL | | }
    = note: use of unstable library feature 'eq_default_body'
    = help: add `#![feature(eq_default_body)]` to the crate attributes to enable
 
-error: aborting due to 3 previous errors
+error: aborting due to 4 previous errors
 
 For more information about this error, try `rustc --explain E0046`.
diff --git a/tests/ui/stability-attribute/default-body-stability-ok-impls.rs b/tests/ui/stability-attribute/default-body-stability-ok-impls.rs
index e1f5c017096..b29d45256bf 100644
--- a/tests/ui/stability-attribute/default-body-stability-ok-impls.rs
+++ b/tests/ui/stability-attribute/default-body-stability-ok-impls.rs
@@ -12,6 +12,8 @@ impl JustTrait for Type {
     const CONSTANT: usize = 1;
 
     fn fun() {}
+
+    fn fun2() {}
 }
 
 impl Equal for Type {