From a88717cef051b8ebbed0e74c57fc0a714bd893dc Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 31 Mar 2022 10:24:46 +0100 Subject: [PATCH] macros: support translatable labels Extends support for generating `DiagnosticMessage::FluentIdentifier` messages from `SessionDiagnostic` derive to `#[label]`. Signed-off-by: David Wood --- compiler/rustc_errors/src/diagnostic.rs | 2 +- .../rustc_errors/src/diagnostic_builder.rs | 2 +- .../rustc_macros/src/session_diagnostic.rs | 109 ++++++++++++------ compiler/rustc_typeck/src/errors.rs | 38 +++--- src/test/ui-fulldeps/session-derive-errors.rs | 42 ++++--- .../ui-fulldeps/session-derive-errors.stderr | 36 +++--- src/test/ui/error-codes/E0184.stderr | 2 +- src/test/ui/exclusive-drop-and-copy.stderr | 4 +- 8 files changed, 140 insertions(+), 95 deletions(-) diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 74abffa7dfa..ecb3cdd627c 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -277,7 +277,7 @@ impl Diagnostic { /// /// This span is *not* considered a ["primary span"][`MultiSpan`]; only /// the `Span` supplied when creating the diagnostic is primary. - pub fn span_label>(&mut self, span: Span, label: T) -> &mut Self { + pub fn span_label(&mut self, span: Span, label: impl Into) -> &mut Self { self.span.push_span_label(span, label.into()); self } diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index 2cee1354cc7..74e0f742946 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -408,7 +408,7 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { /// the diagnostic was constructed. However, the label span is *not* considered a /// ["primary span"][`MultiSpan`]; only the `Span` supplied when creating the diagnostic is /// primary. - pub fn span_label(&mut self, span: Span, label: impl Into) -> &mut Self); + pub fn span_label(&mut self, span: Span, label: impl Into) -> &mut Self); forward!( /// Labels all the given spans with the provided label. diff --git a/compiler/rustc_macros/src/session_diagnostic.rs b/compiler/rustc_macros/src/session_diagnostic.rs index 79086931d5f..efbffabbd03 100644 --- a/compiler/rustc_macros/src/session_diagnostic.rs +++ b/compiler/rustc_macros/src/session_diagnostic.rs @@ -561,6 +561,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { ) -> Result { let diag = &self.diag; let field_binding = &info.binding.binding; + let name = attr.path.segments.last().unwrap().ident.to_string(); let name = name.as_str(); @@ -573,46 +574,38 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { Ok(quote! {}) } "primary_span" => { - if type_matches_path(&info.ty, &["rustc_span", "Span"]) { - return Ok(quote! { - #diag.set_span(*#field_binding); - }); - } else { - throw_span_err!( - attr.span().unwrap(), - "the `#[primary_span]` attribute can only be applied to fields of type `Span`" - ); - } + self.report_error_if_not_applied_to_span(attr, info)?; + Ok(quote! { + #diag.set_span(*#field_binding); + }) + } + "label" => { + self.report_error_if_not_applied_to_span(attr, info)?; + Ok(self.add_subdiagnostic(field_binding, name, "label")) } other => throw_span_err!( attr.span().unwrap(), &format!("`#[{}]` is not a valid `SessionDiagnostic` field attribute", other) ), }, - syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => { - let formatted_str = self.build_format(&s.value(), attr.span()); - match name { - "label" => { - if type_matches_path(&info.ty, &["rustc_span", "Span"]) { - return Ok(quote! { - #diag.span_label(*#field_binding, #formatted_str); - }); - } else { - throw_span_err!( - attr.span().unwrap(), - "the `#[label = ...]` attribute can only be applied to fields of type `Span`" - ); - } - } - other => throw_span_err!( - attr.span().unwrap(), - &format!( - "`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", - other - ) - ), + syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => match name { + "label" => { + self.report_error_if_not_applied_to_span(attr, info)?; + Ok(self.add_subdiagnostic(field_binding, name, &s.value())) } - } + other => throw_span_err!( + attr.span().unwrap(), + &format!( + "`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", + other + ) + ), + }, + syn::Meta::NameValue(_) => throw_span_err!( + attr.span().unwrap(), + &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", name), + |diag| diag.help("value must be a string") + ), syn::Meta::List(list) => { match list.path.segments.iter().last().unwrap().ident.to_string().as_str() { suggestion_kind @ "suggestion" @@ -681,7 +674,55 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { ), } } - _ => panic!("unhandled meta kind"), + } + } + + /// Reports an error if the field's type is not `Span`. + fn report_error_if_not_applied_to_span( + &self, + attr: &syn::Attribute, + info: FieldInfo<'_>, + ) -> Result<(), SessionDiagnosticDeriveError> { + if !type_matches_path(&info.ty, &["rustc_span", "Span"]) { + let name = attr.path.segments.last().unwrap().ident.to_string(); + let name = name.as_str(); + let meta = attr.parse_meta()?; + + throw_span_err!( + attr.span().unwrap(), + &format!( + "the `#[{}{}]` attribute can only be applied to fields of type `Span`", + name, + match meta { + syn::Meta::Path(_) => "", + syn::Meta::NameValue(_) => " = ...", + syn::Meta::List(_) => "(...)", + } + ) + ); + } + + Ok(()) + } + + /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and + /// `fluent_attr_identifier`. + fn add_subdiagnostic( + &self, + field_binding: &proc_macro2::Ident, + kind: &str, + fluent_attr_identifier: &str, + ) -> proc_macro2::TokenStream { + let diag = &self.diag; + + let slug = + self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or_else(|| "missing-slug"); + let fn_name = format_ident!("span_{}", kind); + quote! { + #diag.#fn_name( + *#field_binding, + rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier) + ); } } diff --git a/compiler/rustc_typeck/src/errors.rs b/compiler/rustc_typeck/src/errors.rs index 7531a231e39..72733e087c2 100644 --- a/compiler/rustc_typeck/src/errors.rs +++ b/compiler/rustc_typeck/src/errors.rs @@ -6,9 +6,9 @@ use rustc_span::{symbol::Ident, Span, Symbol}; #[error(code = "E0062", slug = "typeck-field-multiply-specified-in-initializer")] pub struct FieldMultiplySpecifiedInInitializer { #[primary_span] - #[label = "used more than once"] + #[label] pub span: Span, - #[label = "first use of `{ident}`"] + #[label = "previous-use-label"] pub prev_span: Span, pub ident: Ident, } @@ -17,7 +17,7 @@ pub struct FieldMultiplySpecifiedInInitializer { #[error(code = "E0092", slug = "typeck-unrecognized-atomic-operation")] pub struct UnrecognizedAtomicOperation<'a> { #[primary_span] - #[label = "unrecognized atomic operation"] + #[label] pub span: Span, pub op: &'a str, } @@ -26,7 +26,7 @@ pub struct UnrecognizedAtomicOperation<'a> { #[error(code = "E0094", slug = "typeck-wrong-number-of-generic-arguments-to-intrinsic")] pub struct WrongNumberOfGenericArgumentsToIntrinsic<'a> { #[primary_span] - #[label = "expected {expected} {descr} parameter{expected_pluralize}"] + #[label] pub span: Span, pub found: usize, pub expected: usize, @@ -38,7 +38,7 @@ pub struct WrongNumberOfGenericArgumentsToIntrinsic<'a> { #[error(code = "E0093", slug = "typeck-unrecognized-intrinsic-function")] pub struct UnrecognizedIntrinsicFunction { #[primary_span] - #[label = "unrecognized intrinsic"] + #[label] pub span: Span, pub name: Symbol, } @@ -47,9 +47,9 @@ pub struct UnrecognizedIntrinsicFunction { #[error(code = "E0195", slug = "typeck-lifetimes-or-bounds-mismatch-on-trait")] pub struct LifetimesOrBoundsMismatchOnTrait { #[primary_span] - #[label = "lifetimes do not match {item_kind} in trait"] + #[label] pub span: Span, - #[label = "lifetimes in impl do not match this {item_kind} in trait"] + #[label = "generics-label"] pub generics_span: Option, pub item_kind: &'static str, pub ident: Ident, @@ -59,7 +59,7 @@ pub struct LifetimesOrBoundsMismatchOnTrait { #[error(code = "E0120", slug = "typeck-drop-impl-on-wrong-item")] pub struct DropImplOnWrongItem { #[primary_span] - #[label = "must be a struct, enum, or union"] + #[label] pub span: Span, } @@ -68,9 +68,9 @@ pub struct DropImplOnWrongItem { pub struct FieldAlreadyDeclared { pub field_name: Ident, #[primary_span] - #[label = "field already declared"] + #[label] pub span: Span, - #[label = "`{field_name}` first declared here"] + #[label = "previous-decl-label"] pub prev_span: Span, } @@ -78,7 +78,7 @@ pub struct FieldAlreadyDeclared { #[error(code = "E0184", slug = "typeck-copy-impl-on-type-with-dtor")] pub struct CopyImplOnTypeWithDtor { #[primary_span] - #[label = "Copy not allowed on types with destructors"] + #[label] pub span: Span, } @@ -93,7 +93,7 @@ pub struct MultipleRelaxedDefaultBounds { #[error(code = "E0206", slug = "typeck-copy-impl-on-non-adt")] pub struct CopyImplOnNonAdt { #[primary_span] - #[label = "type is not a structure or enumeration"] + #[label] pub span: Span, } @@ -115,7 +115,7 @@ pub struct AmbiguousLifetimeBound { #[error(code = "E0229", slug = "typeck-assoc-type-binding-not-allowed")] pub struct AssocTypeBindingNotAllowed { #[primary_span] - #[label = "associated type not allowed here"] + #[label] pub span: Span, } @@ -130,7 +130,7 @@ pub struct FunctionalRecordUpdateOnNonStruct { #[error(code = "E0516", slug = "typeck-typeof-reserved-keyword-used")] pub struct TypeofReservedKeywordUsed { #[primary_span] - #[label = "reserved keyword"] + #[label] pub span: Span, } @@ -139,9 +139,9 @@ pub struct TypeofReservedKeywordUsed { pub struct ReturnStmtOutsideOfFnBody { #[primary_span] pub span: Span, - #[label = "the return is part of this body..."] + #[label = "encl-body-label"] pub encl_body_span: Option, - #[label = "...not the enclosing function body"] + #[label = "encl-fn-label"] pub encl_fn_span: Option, } @@ -171,9 +171,9 @@ pub struct MethodCallOnUnknownType { #[error(code = "E0719", slug = "typeck-value-of-associated-struct-already-specified")] pub struct ValueOfAssociatedStructAlreadySpecified { #[primary_span] - #[label = "re-bound here"] + #[label] pub span: Span, - #[label = "`{item_name}` bound here first"] + #[label = "previous-bound-label"] pub prev_span: Span, pub item_name: Ident, pub def_path: String, @@ -183,6 +183,6 @@ pub struct ValueOfAssociatedStructAlreadySpecified { #[error(code = "E0745", slug = "typeck-address-of-temporary-taken")] pub struct AddressOfTemporaryTaken { #[primary_span] - #[label = "temporary value"] + #[label] pub span: Span, } diff --git a/src/test/ui-fulldeps/session-derive-errors.rs b/src/test/ui-fulldeps/session-derive-errors.rs index 63912f0ad13..68f24f1f685 100644 --- a/src/test/ui-fulldeps/session-derive-errors.rs +++ b/src/test/ui-fulldeps/session-derive-errors.rs @@ -139,14 +139,14 @@ struct InvalidPathFieldAttr { #[error(code = "E0123", slug = "foo")] struct ErrorWithField { name: String, - #[label = "This error has a field, and references {name}"] + #[label = "bar"] span: Span, } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct ErrorWithMessageAppliedToField { - #[label = "this message is applied to a String field"] + #[label = "bar"] //~^ ERROR the `#[label = ...]` attribute can only be applied to fields of type `Span` name: String, } @@ -154,27 +154,27 @@ struct ErrorWithMessageAppliedToField { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct ErrorWithNonexistentField { - #[label = "This error has a field, and references {name}"] + #[suggestion(message = "This is a suggestion", code = "{name}")] //~^ ERROR `name` doesn't refer to a field on this type - foo: Span, + suggestion: (Span, Applicability), } #[derive(SessionDiagnostic)] -#[error(code = "E0123", slug = "foo")] //~^ ERROR invalid format string: expected `'}'` +#[error(code = "E0123", slug = "foo")] struct ErrorMissingClosingBrace { - #[label = "This is missing a closing brace: {name"] - foo: Span, + #[suggestion(message = "This is a suggestion", code = "{name")] + suggestion: (Span, Applicability), name: String, val: usize, } #[derive(SessionDiagnostic)] -#[error(code = "E0123", slug = "foo")] //~^ ERROR invalid format string: unmatched `}` +#[error(code = "E0123", slug = "foo")] struct ErrorMissingOpeningBrace { - #[label = "This is missing an opening brace: name}"] - foo: Span, + #[suggestion(message = "This is a suggestion", code = "name}")] + suggestion: (Span, Applicability), name: String, val: usize, } @@ -182,14 +182,14 @@ struct ErrorMissingOpeningBrace { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct LabelOnSpan { - #[label = "See here"] + #[label = "bar"] sp: Span, } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct LabelOnNonSpan { - #[label = "See here"] + #[label = "bar"] //~^ ERROR the `#[label = ...]` attribute can only be applied to fields of type `Span` id: u32, } @@ -276,7 +276,7 @@ struct SuggestWithDuplicateApplicabilityAndSpan { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct WrongKindOfAnnotation { - #[label("wrong kind of annotation for label")] + #[label("bar")] //~^ ERROR invalid annotation list `#[label(...)]` z: Span, } @@ -284,7 +284,7 @@ struct WrongKindOfAnnotation { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct OptionsInErrors { - #[label = "Label message"] + #[label = "bar"] label: Option, #[suggestion(message = "suggestion message")] opt_sugg: Option<(Span, Applicability)>, @@ -296,9 +296,9 @@ struct MoveOutOfBorrowError<'tcx> { name: Ident, ty: Ty<'tcx>, #[primary_span] - #[label = "cannot move out of borrow"] + #[label = "bar"] span: Span, - #[label = "`{ty}` first borrowed here"] + #[label = "qux"] other_span: Span, #[suggestion(message = "consider cloning here", code = "{name}.clone()")] opt_sugg: Option<(Span, Applicability)>, @@ -307,7 +307,15 @@ struct MoveOutOfBorrowError<'tcx> { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct ErrorWithLifetime<'a> { - #[label = "Some message that references {name}"] + #[label = "bar"] + span: Span, + name: &'a str, +} + +#[derive(SessionDiagnostic)] +#[error(code = "E0123", slug = "foo")] +struct ErrorWithDefaultLabelAttr<'a> { + #[label] span: Span, name: &'a str, } diff --git a/src/test/ui-fulldeps/session-derive-errors.stderr b/src/test/ui-fulldeps/session-derive-errors.stderr index 09b8050bf51..902bc785ce7 100644 --- a/src/test/ui-fulldeps/session-derive-errors.stderr +++ b/src/test/ui-fulldeps/session-derive-errors.stderr @@ -162,35 +162,31 @@ LL | #[nonsense] error: the `#[label = ...]` attribute can only be applied to fields of type `Span` --> $DIR/session-derive-errors.rs:149:5 | -LL | #[label = "this message is applied to a String field"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[label = "bar"] + | ^^^^^^^^^^^^^^^^ error: `name` doesn't refer to a field on this type - --> $DIR/session-derive-errors.rs:157:5 + --> $DIR/session-derive-errors.rs:157:52 | -LL | #[label = "This error has a field, and references {name}"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[suggestion(message = "This is a suggestion", code = "{name}")] + | ^^^^^^^^^^^^^^^ error: invalid format string: expected `'}'` but string was terminated - --> $DIR/session-derive-errors.rs:163:20 + --> $DIR/session-derive-errors.rs:162:16 | LL | #[derive(SessionDiagnostic)] - | ----------------- in this derive macro expansion -LL | #[error(code = "E0123", slug = "foo")] - | - ^ expected `'}'` in format string - | | - | because of this opening brace + | - ^ expected `'}'` in format string + | | + | because of this opening brace | = note: if you intended to print `{`, you can escape it using `{{` = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid format string: unmatched `}` found - --> $DIR/session-derive-errors.rs:173:20 + --> $DIR/session-derive-errors.rs:172:15 | LL | #[derive(SessionDiagnostic)] - | ----------------- in this derive macro expansion -LL | #[error(code = "E0123", slug = "foo")] - | ^ unmatched `}` in format string + | ^ unmatched `}` in format string | = note: if you intended to print `}`, you can escape it using `}}` = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -198,8 +194,8 @@ LL | #[error(code = "E0123", slug = "foo")] error: the `#[label = ...]` attribute can only be applied to fields of type `Span` --> $DIR/session-derive-errors.rs:192:5 | -LL | #[label = "See here"] - | ^^^^^^^^^^^^^^^^^^^^^ +LL | #[label = "bar"] + | ^^^^^^^^^^^^^^^^ error: `nonsense` is not a valid key for `#[suggestion(...)]` --> $DIR/session-derive-errors.rs:217:18 @@ -250,8 +246,8 @@ LL | | suggestion: (Applicability, Applicability, Span), error: invalid annotation list `#[label(...)]` --> $DIR/session-derive-errors.rs:279:7 | -LL | #[label("wrong kind of annotation for label")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[label("bar")] + | ^^^^^^^^^^^^ error: cannot find attribute `nonsense` in this scope --> $DIR/session-derive-errors.rs:51:3 @@ -266,7 +262,7 @@ LL | #[nonsense] | ^^^^^^^^ error[E0599]: no method named `into_diagnostic_arg` found for struct `Hello` in the current scope - --> $DIR/session-derive-errors.rs:315:10 + --> $DIR/session-derive-errors.rs:323:10 | LL | struct Hello {} | ------------ method `into_diagnostic_arg` not found for this diff --git a/src/test/ui/error-codes/E0184.stderr b/src/test/ui/error-codes/E0184.stderr index 5bfeaa58bdf..bb3017b6ec2 100644 --- a/src/test/ui/error-codes/E0184.stderr +++ b/src/test/ui/error-codes/E0184.stderr @@ -2,7 +2,7 @@ error[E0184]: the trait `Copy` may not be implemented for this type; the type ha --> $DIR/E0184.rs:1:10 | LL | #[derive(Copy)] - | ^^^^ Copy not allowed on types with destructors + | ^^^^ `Copy` not allowed on types with destructors | = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/src/test/ui/exclusive-drop-and-copy.stderr b/src/test/ui/exclusive-drop-and-copy.stderr index 36ee6570e42..8649c8abbfa 100644 --- a/src/test/ui/exclusive-drop-and-copy.stderr +++ b/src/test/ui/exclusive-drop-and-copy.stderr @@ -2,7 +2,7 @@ error[E0184]: the trait `Copy` may not be implemented for this type; the type ha --> $DIR/exclusive-drop-and-copy.rs:3:10 | LL | #[derive(Copy, Clone)] - | ^^^^ Copy not allowed on types with destructors + | ^^^^ `Copy` not allowed on types with destructors | = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -10,7 +10,7 @@ error[E0184]: the trait `Copy` may not be implemented for this type; the type ha --> $DIR/exclusive-drop-and-copy.rs:10:10 | LL | #[derive(Copy, Clone)] - | ^^^^ Copy not allowed on types with destructors + | ^^^^ `Copy` not allowed on types with destructors | = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)