Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 9956d4f

Browse files
committed
macros: add args for non-subdiagnostic fields
Non-subdiagnostic fields (i.e. those that don't have `#[label]` attributes or similar and are just additional context) have to be added as arguments for Fluent messages to refer them. This commit extends the `SessionDiagnostic` derive to do this for all fields that do not have attributes and introduces an `IntoDiagnosticArg` trait that is implemented on all types that can be converted to a argument for Fluent. Signed-off-by: David Wood <david.wood@huawei.com>
1 parent 8677fef commit 9956d4f

File tree

6 files changed

+122
-30
lines changed

6 files changed

+122
-30
lines changed

compiler/rustc_errors/src/diagnostic.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustc_error_messages::FluentValue;
88
use rustc_lint_defs::{Applicability, LintExpectationId};
99
use rustc_serialize::json::Json;
1010
use rustc_span::edition::LATEST_STABLE_EDITION;
11+
use rustc_span::symbol::{Ident, Symbol};
1112
use rustc_span::{Span, DUMMY_SP};
1213
use std::borrow::Cow;
1314
use std::fmt;
@@ -31,6 +32,44 @@ pub enum DiagnosticArgValue<'source> {
3132
Number(usize),
3233
}
3334

35+
/// Converts a value of a type into a `DiagnosticArg` (typically a field of a `SessionDiagnostic`
36+
/// struct). Implemented as a custom trait rather than `From` so that it is implemented on the type
37+
/// being converted rather than on `DiagnosticArgValue`, which enables types from other `rustc_*`
38+
/// crates to implement this.
39+
pub trait IntoDiagnosticArg {
40+
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static>;
41+
}
42+
43+
impl IntoDiagnosticArg for String {
44+
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
45+
DiagnosticArgValue::Str(Cow::Owned(self))
46+
}
47+
}
48+
49+
impl IntoDiagnosticArg for Symbol {
50+
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
51+
self.to_ident_string().into_diagnostic_arg()
52+
}
53+
}
54+
55+
impl IntoDiagnosticArg for Ident {
56+
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
57+
self.to_string().into_diagnostic_arg()
58+
}
59+
}
60+
61+
impl<'a> IntoDiagnosticArg for &'a str {
62+
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
63+
self.to_string().into_diagnostic_arg()
64+
}
65+
}
66+
67+
impl IntoDiagnosticArg for usize {
68+
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
69+
DiagnosticArgValue::Number(self)
70+
}
71+
}
72+
3473
impl<'source> Into<FluentValue<'source>> for DiagnosticArgValue<'source> {
3574
fn into(self) -> FluentValue<'source> {
3675
match self {
@@ -788,6 +827,15 @@ impl Diagnostic {
788827
&self.args
789828
}
790829

830+
pub fn set_arg(
831+
&mut self,
832+
name: impl Into<Cow<'static, str>>,
833+
arg: DiagnosticArgValue<'static>,
834+
) -> &mut Self {
835+
self.args.push((name.into(), arg.into()));
836+
self
837+
}
838+
791839
pub fn styled_message(&self) -> &Vec<(DiagnosticMessage, Style)> {
792840
&self.message
793841
}

compiler/rustc_errors/src/diagnostic_builder.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
use crate::diagnostic::DiagnosticArgValue;
12
use crate::{Diagnostic, DiagnosticId, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed};
23
use crate::{Handler, Level, MultiSpan, StashKey};
34
use rustc_lint_defs::Applicability;
45

56
use rustc_span::Span;
7+
use std::borrow::Cow;
68
use std::fmt::{self, Debug};
79
use std::marker::PhantomData;
810
use std::ops::{Deref, DerefMut};
@@ -536,6 +538,11 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
536538
forward!(pub fn set_primary_message(&mut self, msg: impl Into<String>) -> &mut Self);
537539
forward!(pub fn set_span(&mut self, sp: impl Into<MultiSpan>) -> &mut Self);
538540
forward!(pub fn code(&mut self, s: DiagnosticId) -> &mut Self);
541+
forward!(pub fn set_arg(
542+
&mut self,
543+
name: impl Into<Cow<'static, str>>,
544+
arg: DiagnosticArgValue<'static>,
545+
) -> &mut Self);
539546
}
540547

541548
impl<G: EmissionGuarantee> Debug for DiagnosticBuilder<'_, G> {

compiler/rustc_errors/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,8 @@ impl fmt::Display for ExplicitBug {
406406
impl error::Error for ExplicitBug {}
407407

408408
pub use diagnostic::{
409-
Diagnostic, DiagnosticArg, DiagnosticId, DiagnosticStyledString, SubDiagnostic,
409+
Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId, DiagnosticStyledString,
410+
IntoDiagnosticArg, SubDiagnostic,
410411
};
411412
pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee};
412413
use std::backtrace::Backtrace;

compiler/rustc_macros/src/session_diagnostic.rs

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ impl<'a> SessionDiagnosticDerive<'a> {
157157
}
158158
}
159159
fn into_tokens(self) -> proc_macro2::TokenStream {
160-
let SessionDiagnosticDerive { structure, mut builder } = self;
160+
let SessionDiagnosticDerive { mut structure, mut builder } = self;
161161

162162
let ast = structure.ast();
163163
let attrs = &ast.attrs;
@@ -175,11 +175,17 @@ impl<'a> SessionDiagnosticDerive<'a> {
175175
}
176176
};
177177

178-
let body = structure.each(|field_binding| {
178+
// Generates calls to `span_label` and similar functions based on the attributes
179+
// on fields. Code for suggestions uses formatting machinery and the value of
180+
// other fields - because any given field can be referenced multiple times, it
181+
// should be accessed through a borrow. When passing fields to `set_arg` (which
182+
// happens below) for Fluent, we want to move the data, so that has to happen
183+
// in a separate pass over the fields.
184+
let attrs = structure.each(|field_binding| {
179185
let field = field_binding.ast();
180186
let result = field.attrs.iter().map(|attr| {
181187
builder
182-
.generate_field_code(
188+
.generate_field_attr_code(
183189
attr,
184190
FieldInfo {
185191
vis: &field.vis,
@@ -190,10 +196,30 @@ impl<'a> SessionDiagnosticDerive<'a> {
190196
)
191197
.unwrap_or_else(|v| v.to_compile_error())
192198
});
193-
return quote! {
194-
#(#result);*
195-
};
199+
200+
quote! { #(#result);* }
196201
});
202+
203+
// When generating `set_arg` calls, move data rather than borrow it to avoid
204+
// requiring clones - this must therefore be the last use of each field (for
205+
// example, any formatting machinery that might refer to a field should be
206+
// generated already).
207+
structure.bind_with(|_| synstructure::BindStyle::Move);
208+
let args = structure.each(|field_binding| {
209+
let field = field_binding.ast();
210+
// When a field has attributes like `#[label]` or `#[note]` then it doesn't
211+
// need to be passed as an argument to the diagnostic. But when a field has no
212+
// attributes then it must be passed as an argument to the diagnostic so that
213+
// it can be referred to by Fluent messages.
214+
if field.attrs.is_empty() {
215+
let diag = &builder.diag;
216+
let ident = &field_binding.binding;
217+
quote! { #diag.set_arg(stringify!(#ident), #field_binding.into_diagnostic_arg()); }
218+
} else {
219+
quote! {}
220+
}
221+
});
222+
197223
// Finally, putting it altogether.
198224
match builder.kind {
199225
None => {
@@ -210,7 +236,10 @@ impl<'a> SessionDiagnosticDerive<'a> {
210236
let mut #diag = #sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error(#code));
211237
#preamble
212238
match self {
213-
#body
239+
#attrs
240+
}
241+
match self {
242+
#args
214243
}
215244
#diag
216245
}
@@ -236,6 +265,7 @@ impl<'a> SessionDiagnosticDerive<'a> {
236265
self,
237266
#sess: &'__session_diagnostic_sess rustc_session::Session
238267
) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, rustc_errors::ErrorGuaranteed> {
268+
use rustc_errors::IntoDiagnosticArg;
239269
#implementation
240270
}
241271
}
@@ -345,15 +375,13 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
345375
}
346376
}
347377

348-
fn generate_field_code(
378+
fn generate_field_attr_code(
349379
&mut self,
350380
attr: &syn::Attribute,
351381
info: FieldInfo<'_>,
352382
) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
353383
let field_binding = &info.binding.binding;
354-
355384
let option_ty = option_inner_ty(&info.ty);
356-
357385
let generated_code = self.generate_non_option_field_code(
358386
attr,
359387
FieldInfo {
@@ -363,15 +391,16 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
363391
span: info.span,
364392
},
365393
)?;
366-
Ok(if option_ty.is_none() {
367-
quote! { #generated_code }
394+
395+
if option_ty.is_none() {
396+
Ok(quote! { #generated_code })
368397
} else {
369-
quote! {
398+
Ok(quote! {
370399
if let Some(#field_binding) = #field_binding {
371400
#generated_code
372401
}
373-
}
374-
})
402+
})
403+
}
375404
}
376405

377406
fn generate_non_option_field_code(
@@ -383,19 +412,20 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
383412
let field_binding = &info.binding.binding;
384413
let name = attr.path.segments.last().unwrap().ident.to_string();
385414
let name = name.as_str();
415+
386416
// At this point, we need to dispatch based on the attribute key + the
387417
// type.
388418
let meta = attr.parse_meta()?;
389-
Ok(match meta {
419+
match meta {
390420
syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
391421
let formatted_str = self.build_format(&s.value(), attr.span());
392422
match name {
393423
"message" => {
394424
if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
395-
quote! {
425+
return Ok(quote! {
396426
#diag.set_span(*#field_binding);
397427
#diag.set_primary_message(#formatted_str);
398-
}
428+
});
399429
} else {
400430
throw_span_err!(
401431
attr.span().unwrap(),
@@ -405,9 +435,9 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
405435
}
406436
"label" => {
407437
if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
408-
quote! {
438+
return Ok(quote! {
409439
#diag.span_label(*#field_binding, #formatted_str);
410-
}
440+
});
411441
} else {
412442
throw_span_err!(
413443
attr.span().unwrap(),
@@ -480,11 +510,11 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
480510
);
481511
};
482512
let code = code.unwrap_or_else(|| quote! { String::new() });
483-
// Now build it out:
513+
484514
let suggestion_method = format_ident!("span_{}", suggestion_kind);
485-
quote! {
515+
return Ok(quote! {
486516
#diag.#suggestion_method(#span, #msg, #code, #applicability);
487-
}
517+
});
488518
}
489519
other => throw_span_err!(
490520
list.span().unwrap(),
@@ -493,7 +523,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
493523
}
494524
}
495525
_ => panic!("unhandled meta kind"),
496-
})
526+
}
497527
}
498528

499529
fn span_and_applicability_of_ty(

compiler/rustc_middle/src/ty/diagnostics.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ use crate::ty::{
88
};
99

1010
use rustc_data_structures::fx::FxHashMap;
11-
use rustc_errors::{Applicability, Diagnostic};
11+
use rustc_errors::{Applicability, Diagnostic, DiagnosticArgValue, IntoDiagnosticArg};
1212
use rustc_hir as hir;
1313
use rustc_hir::def_id::DefId;
1414
use rustc_hir::{QPath, TyKind, WhereBoundPredicate, WherePredicate};
1515
use rustc_span::Span;
1616

17+
impl<'tcx> IntoDiagnosticArg for Ty<'tcx> {
18+
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
19+
format!("{}", self).into_diagnostic_arg()
20+
}
21+
}
22+
1723
impl<'tcx> Ty<'tcx> {
1824
/// Similar to `Ty::is_primitive`, but also considers inferred numeric values to be primitive.
1925
pub fn is_primitive_ty(self) -> bool {

src/test/ui-fulldeps/session-derive-errors.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
#![crate_type = "lib"]
1212

1313
extern crate rustc_span;
14-
use rustc_span::Span;
1514
use rustc_span::symbol::Ident;
15+
use rustc_span::Span;
1616

1717
extern crate rustc_macros;
1818
use rustc_macros::SessionDiagnostic;
@@ -108,7 +108,7 @@ struct ErrorWithMessageAppliedToField {
108108
#[message = "This error has a field, and references {name}"]
109109
//~^ ERROR `name` doesn't refer to a field on this type
110110
struct ErrorWithNonexistentField {
111-
span: Span
111+
descr: String,
112112
}
113113

114114
#[derive(SessionDiagnostic)]
@@ -117,7 +117,7 @@ struct ErrorWithNonexistentField {
117117
//~^ ERROR invalid format string: expected `'}'`
118118
struct ErrorMissingClosingBrace {
119119
name: String,
120-
span: Span
120+
val: usize,
121121
}
122122

123123
#[derive(SessionDiagnostic)]
@@ -126,7 +126,7 @@ struct ErrorMissingClosingBrace {
126126
//~^ ERROR invalid format string: unmatched `}`
127127
struct ErrorMissingOpeningBrace {
128128
name: String,
129-
span: Span
129+
val: usize,
130130
}
131131

132132
#[derive(SessionDiagnostic)]

0 commit comments

Comments
 (0)