Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate fluent variable references in tests #111269

Merged
merged 3 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions compiler/rustc_fluent_macro/src/fluent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),*];
})
}
}

Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
61 changes: 57 additions & 4 deletions compiler/rustc_macros/src/diagnostics/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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()
}
Expand All @@ -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>(
Expand All @@ -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
}
}

Expand All @@ -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 ]
clubby789 marked this conversation as resolved.
Show resolved Hide resolved
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));
}
}
}
}
8 changes: 0 additions & 8 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,14 +1153,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,
Expand Down
2 changes: 2 additions & 0 deletions tests/ui-fulldeps/session-diagnostic/example.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 21 additions & 0 deletions tests/ui-fulldeps/session-diagnostic/invalid-variable.rs
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 4 additions & 0 deletions tests/ui/stability-attribute/auxiliary/default_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
1 change: 1 addition & 0 deletions tests/ui/stability-attribute/default-body-stability-err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
14 changes: 12 additions & 2 deletions tests/ui/stability-attribute/default-body-stability-err.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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 | |
Expand All @@ -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`.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ impl JustTrait for Type {
const CONSTANT: usize = 1;

fn fun() {}

fn fun2() {}
}

impl Equal for Type {
Expand Down
Loading