Skip to content

Commit 825642c

Browse files
authored
Rollup merge of rust-lang#111269 - clubby789:validate-fluent-variables, r=davidtwco
Validate fluent variable references in tests Closes rust-lang#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.
2 parents bb95b7d + 8969d97 commit 825642c

File tree

10 files changed

+139
-18
lines changed

10 files changed

+139
-18
lines changed

compiler/rustc_fluent_macro/src/fluent.rs

+39-3
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
179179
let mut previous_defns = HashMap::new();
180180
let mut message_refs = Vec::new();
181181
for entry in resource.entries() {
182-
if let Entry::Message(Message { id: Identifier { name }, attributes, value, .. }) = entry {
182+
if let Entry::Message(msg) = entry {
183+
let Message { id: Identifier { name }, attributes, value, .. } = msg;
183184
let _ = previous_defns.entry(name.to_string()).or_insert(resource_span);
184185
if name.contains('-') {
185186
Diagnostic::spanned(
@@ -229,9 +230,10 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
229230
continue;
230231
}
231232

232-
let msg = format!("Constant referring to Fluent message `{name}` from `{crate_name}`");
233+
let docstr =
234+
format!("Constant referring to Fluent message `{name}` from `{crate_name}`");
233235
constants.extend(quote! {
234-
#[doc = #msg]
236+
#[doc = #docstr]
235237
pub const #snake_name: crate::DiagnosticMessage =
236238
crate::DiagnosticMessage::FluentIdentifier(
237239
std::borrow::Cow::Borrowed(#name),
@@ -269,6 +271,15 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
269271
);
270272
});
271273
}
274+
275+
// Record variables referenced by these messages so we can produce
276+
// tests in the derive diagnostics to validate them.
277+
let ident = quote::format_ident!("{snake_name}_refs");
278+
let vrefs = variable_references(msg);
279+
constants.extend(quote! {
280+
#[cfg(test)]
281+
pub const #ident: &[&str] = &[#(#vrefs),*];
282+
})
272283
}
273284
}
274285

@@ -334,3 +345,28 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
334345
}
335346
.into()
336347
}
348+
349+
fn variable_references<'a>(msg: &Message<&'a str>) -> Vec<&'a str> {
350+
let mut refs = vec![];
351+
if let Some(Pattern { elements }) = &msg.value {
352+
for elt in elements {
353+
if let PatternElement::Placeable {
354+
expression: Expression::Inline(InlineExpression::VariableReference { id }),
355+
} = elt
356+
{
357+
refs.push(id.name);
358+
}
359+
}
360+
}
361+
for attr in &msg.attributes {
362+
for elt in &attr.value.elements {
363+
if let PatternElement::Placeable {
364+
expression: Expression::Inline(InlineExpression::VariableReference { id }),
365+
} = elt
366+
{
367+
refs.push(id.name);
368+
}
369+
}
370+
}
371+
refs
372+
}

compiler/rustc_hir_analysis/messages.ftl

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ hir_analysis_missing_trait_item_suggestion = implement the missing item: `{$snip
137137
138138
hir_analysis_missing_trait_item_unstable = not all trait items implemented, missing: `{$missing_item_name}`
139139
.note = default implementation of `{$missing_item_name}` is unstable
140-
.some_note = use of unstable library feature '{$feature}': {$r}
140+
.some_note = use of unstable library feature '{$feature}': {$reason}
141141
.none_note = use of unstable library feature '{$feature}'
142142
143143
hir_analysis_missing_type_params =

compiler/rustc_macros/src/diagnostics/diagnostic.rs

+57-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![deny(unused_must_use)]
22

3+
use std::cell::RefCell;
4+
35
use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind};
46
use crate::diagnostics::error::{span_err, DiagnosticDeriveError};
57
use crate::diagnostics::utils::SetOnce;
@@ -28,6 +30,7 @@ impl<'a> DiagnosticDerive<'a> {
2830
pub(crate) fn into_tokens(self) -> TokenStream {
2931
let DiagnosticDerive { mut structure, mut builder } = self;
3032

33+
let slugs = RefCell::new(Vec::new());
3134
let implementation = builder.each_variant(&mut structure, |mut builder, variant| {
3235
let preamble = builder.preamble(variant);
3336
let body = builder.body(variant);
@@ -56,6 +59,7 @@ impl<'a> DiagnosticDerive<'a> {
5659
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
5760
}
5861
Some(slug) => {
62+
slugs.borrow_mut().push(slug.clone());
5963
quote! {
6064
let mut #diag = #handler.struct_diagnostic(crate::fluent_generated::#slug);
6165
}
@@ -73,7 +77,8 @@ impl<'a> DiagnosticDerive<'a> {
7377
});
7478

7579
let DiagnosticDeriveKind::Diagnostic { handler } = &builder.kind else { unreachable!() };
76-
structure.gen_impl(quote! {
80+
81+
let mut imp = structure.gen_impl(quote! {
7782
gen impl<'__diagnostic_handler_sess, G>
7883
rustc_errors::IntoDiagnostic<'__diagnostic_handler_sess, G>
7984
for @Self
@@ -89,7 +94,11 @@ impl<'a> DiagnosticDerive<'a> {
8994
#implementation
9095
}
9196
}
92-
})
97+
});
98+
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
99+
imp.extend(test);
100+
}
101+
imp
93102
}
94103
}
95104

@@ -124,6 +133,7 @@ impl<'a> LintDiagnosticDerive<'a> {
124133
}
125134
});
126135

136+
let slugs = RefCell::new(Vec::new());
127137
let msg = builder.each_variant(&mut structure, |mut builder, variant| {
128138
// Collect the slug by generating the preamble.
129139
let _ = builder.preamble(variant);
@@ -148,6 +158,7 @@ impl<'a> LintDiagnosticDerive<'a> {
148158
DiagnosticDeriveError::ErrorHandled.to_compile_error()
149159
}
150160
Some(slug) => {
161+
slugs.borrow_mut().push(slug.clone());
151162
quote! {
152163
crate::fluent_generated::#slug.into()
153164
}
@@ -156,7 +167,7 @@ impl<'a> LintDiagnosticDerive<'a> {
156167
});
157168

158169
let diag = &builder.diag;
159-
structure.gen_impl(quote! {
170+
let mut imp = structure.gen_impl(quote! {
160171
gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
161172
#[track_caller]
162173
fn decorate_lint<'__b>(
@@ -171,7 +182,12 @@ impl<'a> LintDiagnosticDerive<'a> {
171182
#msg
172183
}
173184
}
174-
})
185+
});
186+
for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
187+
imp.extend(test);
188+
}
189+
190+
imp
175191
}
176192
}
177193

@@ -198,3 +214,40 @@ impl Mismatch {
198214
}
199215
}
200216
}
217+
218+
/// Generates a `#[test]` that verifies that all referenced variables
219+
/// exist on this structure.
220+
fn generate_test(slug: &syn::Path, structure: &Structure<'_>) -> TokenStream {
221+
// FIXME: We can't identify variables in a subdiagnostic
222+
for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) {
223+
for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) {
224+
if attr_name == "subdiagnostic" {
225+
return quote!();
226+
}
227+
}
228+
}
229+
use std::sync::atomic::{AtomicUsize, Ordering};
230+
// We need to make sure that the same diagnostic slug can be used multiple times without causing an
231+
// error, so just have a global counter here.
232+
static COUNTER: AtomicUsize = AtomicUsize::new(0);
233+
let slug = slug.get_ident().unwrap();
234+
let ident = quote::format_ident!("verify_{slug}_{}", COUNTER.fetch_add(1, Ordering::Relaxed));
235+
let ref_slug = quote::format_ident!("{slug}_refs");
236+
let struct_name = &structure.ast().ident;
237+
let variables: Vec<_> = structure
238+
.variants()
239+
.iter()
240+
.flat_map(|v| v.ast().fields.iter().filter_map(|f| f.ident.as_ref().map(|i| i.to_string())))
241+
.collect();
242+
// tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this
243+
quote! {
244+
#[cfg(test)]
245+
#[test ]
246+
fn #ident() {
247+
let variables = [#(#variables),*];
248+
for vref in crate::fluent_generated::#ref_slug {
249+
assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug));
250+
}
251+
}
252+
}
253+
}

compiler/rustc_passes/src/errors.rs

-8
Original file line numberDiff line numberDiff line change
@@ -1150,14 +1150,6 @@ pub struct UnixSigpipeValues {
11501150
pub span: Span,
11511151
}
11521152

1153-
#[derive(Diagnostic)]
1154-
#[diag(passes_no_main_function, code = "E0601")]
1155-
pub struct NoMainFunction {
1156-
#[primary_span]
1157-
pub span: Span,
1158-
pub crate_name: String,
1159-
}
1160-
11611153
pub struct NoMainErr {
11621154
pub sp: Span,
11631155
pub crate_name: Symbol,

tests/ui-fulldeps/session-diagnostic/example.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ no_crate_example = this is an example message used in testing
33
.help = with a help
44
.suggestion = with a suggestion
55
.label = with a label
6+
7+
no_crate_bad_reference = {$r} does not exist
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// run-fail
2+
// compile-flags: --test
3+
// test that messages referencing non-existent fields cause test failures
4+
5+
#![feature(rustc_private)]
6+
#![crate_type = "lib"]
7+
8+
extern crate rustc_driver;
9+
extern crate rustc_fluent_macro;
10+
extern crate rustc_macros;
11+
extern crate rustc_errors;
12+
use rustc_fluent_macro::fluent_messages;
13+
use rustc_macros::Diagnostic;
14+
use rustc_errors::{SubdiagnosticMessage, DiagnosticMessage};
15+
extern crate rustc_session;
16+
17+
fluent_messages! { "./example.ftl" }
18+
19+
#[derive(Diagnostic)]
20+
#[diag(no_crate_bad_reference)]
21+
struct BadRef;

tests/ui/stability-attribute/auxiliary/default_body.rs

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ pub trait JustTrait {
1111
#[rustc_default_body_unstable(feature = "fun_default_body", issue = "none")]
1212
#[stable(feature = "stable_feature", since = "1.0.0")]
1313
fn fun() {}
14+
15+
#[rustc_default_body_unstable(feature = "fun_default_body", issue = "none", reason = "reason")]
16+
#[stable(feature = "stable_feature", since = "1.0.0")]
17+
fn fun2() {}
1418
}
1519

1620
#[rustc_must_implement_one_of(eq, neq)]

tests/ui/stability-attribute/default-body-stability-err.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct Type;
1010
impl JustTrait for Type {}
1111
//~^ ERROR not all trait items implemented, missing: `CONSTANT` [E0046]
1212
//~| ERROR not all trait items implemented, missing: `fun` [E0046]
13+
//~| ERROR not all trait items implemented, missing: `fun2` [E0046]
1314

1415
impl Equal for Type {
1516
//~^ ERROR not all trait items implemented, missing: `eq` [E0046]

tests/ui/stability-attribute/default-body-stability-err.stderr

+12-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,18 @@ LL | impl JustTrait for Type {}
1818
= note: use of unstable library feature 'fun_default_body'
1919
= help: add `#![feature(fun_default_body)]` to the crate attributes to enable
2020

21+
error[E0046]: not all trait items implemented, missing: `fun2`
22+
--> $DIR/default-body-stability-err.rs:10:1
23+
|
24+
LL | impl JustTrait for Type {}
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
|
27+
= note: default implementation of `fun2` is unstable
28+
= note: use of unstable library feature 'fun_default_body': reason
29+
= help: add `#![feature(fun_default_body)]` to the crate attributes to enable
30+
2131
error[E0046]: not all trait items implemented, missing: `eq`
22-
--> $DIR/default-body-stability-err.rs:14:1
32+
--> $DIR/default-body-stability-err.rs:15:1
2333
|
2434
LL | / impl Equal for Type {
2535
LL | |
@@ -33,6 +43,6 @@ LL | | }
3343
= note: use of unstable library feature 'eq_default_body'
3444
= help: add `#![feature(eq_default_body)]` to the crate attributes to enable
3545

36-
error: aborting due to 3 previous errors
46+
error: aborting due to 4 previous errors
3747

3848
For more information about this error, try `rustc --explain E0046`.

tests/ui/stability-attribute/default-body-stability-ok-impls.rs

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ impl JustTrait for Type {
1212
const CONSTANT: usize = 1;
1313

1414
fn fun() {}
15+
16+
fn fun2() {}
1517
}
1618

1719
impl Equal for Type {

0 commit comments

Comments
 (0)