From 22685b9607936ae1330e2f84773c04292f5cdd30 Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 31 Mar 2022 13:10:13 +0100 Subject: [PATCH] macros: support translatable suggestions Extends support for generating `DiagnosticMessage::FluentIdentifier` messages from `SessionDiagnostic` derive to `#[suggestion]`. Signed-off-by: David Wood --- .../rustc_macros/src/session_diagnostic.rs | 159 ++++++++++-------- src/test/ui-fulldeps/session-derive-errors.rs | 45 +++-- .../ui-fulldeps/session-derive-errors.stderr | 60 +++---- 3 files changed, 141 insertions(+), 123 deletions(-) diff --git a/compiler/rustc_macros/src/session_diagnostic.rs b/compiler/rustc_macros/src/session_diagnostic.rs index c7ee72907b5..46f698f6f9b 100644 --- a/compiler/rustc_macros/src/session_diagnostic.rs +++ b/compiler/rustc_macros/src/session_diagnostic.rs @@ -594,6 +594,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { info: FieldInfo<'_>, ) -> Result { let diag = &self.diag; + let span = attr.span().unwrap(); let field_binding = &info.binding.binding; let name = attr.path.segments.last().unwrap().ident.to_string(); @@ -618,7 +619,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { Ok(self.add_subdiagnostic(field_binding, name, name)) } other => throw_span_err!( - attr.span().unwrap(), + span, &format!("`#[{}]` is not a valid `SessionDiagnostic` field attribute", other) ), }, @@ -628,7 +629,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { Ok(self.add_subdiagnostic(field_binding, name, &s.value())) } other => throw_span_err!( - attr.span().unwrap(), + span, &format!( "`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", other @@ -636,77 +637,103 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> { ), }, syn::Meta::NameValue(_) => throw_span_err!( - attr.span().unwrap(), + span, &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" - | suggestion_kind @ "suggestion_short" - | suggestion_kind @ "suggestion_hidden" - | suggestion_kind @ "suggestion_verbose" => { - let (span, applicability) = self.span_and_applicability_of_ty(info)?; + syn::Meta::List(syn::MetaList { path, nested, .. }) => { + let name = path.segments.last().unwrap().ident.to_string(); + let name = name.as_ref(); - let mut msg = None; - let mut code = None; - - for arg in list.nested.iter() { - if let syn::NestedMeta::Meta(syn::Meta::NameValue(arg_name_value)) = arg - { - if let syn::MetaNameValue { lit: syn::Lit::Str(s), .. } = - arg_name_value - { - let name = arg_name_value - .path - .segments - .last() - .unwrap() - .ident - .to_string(); - let name = name.as_str(); - let formatted_str = self.build_format(&s.value(), arg.span()); - match name { - "message" => { - msg = Some(formatted_str); - } - "code" => { - code = Some(formatted_str); - } - other => throw_span_err!( - arg.span().unwrap(), - &format!( - "`{}` is not a valid key for `#[suggestion(...)]`", - other - ) - ), - } - } - } - } - let msg = if let Some(msg) = msg { - quote!(#msg.as_str()) - } else { - throw_span_err!( - list.span().unwrap(), - "missing suggestion message", - |diag| { - diag.help("provide a suggestion message using `#[suggestion(message = \"...\")]`") - } - ); - }; - let code = code.unwrap_or_else(|| quote! { String::new() }); - - let suggestion_method = format_ident!("span_{}", suggestion_kind); - return Ok(quote! { - #diag.#suggestion_method(#span, #msg, #code, #applicability); - }); - } + match name { + "suggestion" | "suggestion_short" | "suggestion_hidden" + | "suggestion_verbose" => (), other => throw_span_err!( - list.span().unwrap(), - &format!("invalid annotation list `#[{}(...)]`", other) + span, + &format!( + "`#[{}(...)]` is not a valid `SessionDiagnostic` field attribute", + other + ) ), + }; + + let (span_, applicability) = self.span_and_applicability_of_ty(info)?; + + let mut msg = None; + let mut code = None; + + for attr in nested { + let meta = match attr { + syn::NestedMeta::Meta(meta) => meta, + syn::NestedMeta::Lit(_) => throw_span_err!( + span, + &format!( + "`#[{}(\"...\")]` is not a valid `SessionDiagnostic` field attribute", + name + ) + ), + }; + + let span = meta.span().unwrap(); + let nested_name = meta.path().segments.last().unwrap().ident.to_string(); + let nested_name = nested_name.as_str(); + + match meta { + syn::Meta::NameValue(syn::MetaNameValue { + lit: syn::Lit::Str(s), .. + }) => match nested_name { + "message" => { + msg = Some(s.value()); + } + "code" => { + let formatted_str = self.build_format(&s.value(), s.span()); + code = Some(formatted_str); + } + other => throw_span_err!( + span, + &format!( + "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` field attribute", + name, other + ) + ), + }, + syn::Meta::NameValue(..) => throw_span_err!( + span, + &format!( + "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute", + name, nested_name + ), + |diag| diag.help("value must be a string") + ), + syn::Meta::Path(..) => throw_span_err!( + span, + &format!( + "`#[{}({})]` is not a valid `SessionDiagnostic` struct attribute", + name, nested_name + ) + ), + syn::Meta::List(..) => throw_span_err!( + span, + &format!( + "`#[{}({}(...))]` is not a valid `SessionDiagnostic` struct attribute", + name, nested_name + ) + ), + } } + + let method = format_ident!("span_{}", name); + + let slug = self + .slug + .as_ref() + .map(|(slug, _)| slug.as_str()) + .unwrap_or_else(|| "missing-slug"); + let msg = msg.as_deref().unwrap_or("suggestion"); + let msg = quote! { rustc_errors::DiagnosticMessage::fluent_attr(#slug, #msg) }; + let code = code.unwrap_or_else(|| quote! { String::new() }); + + Ok(quote! { #diag.#method(#span_, #msg, #code, #applicability); }) } } } diff --git a/src/test/ui-fulldeps/session-derive-errors.rs b/src/test/ui-fulldeps/session-derive-errors.rs index a3ae3c9a14d..adec548b390 100644 --- a/src/test/ui-fulldeps/session-derive-errors.rs +++ b/src/test/ui-fulldeps/session-derive-errors.rs @@ -83,7 +83,7 @@ struct InvalidNestedStructAttr3 {} #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct WrongPlaceField { - #[suggestion = "this is the wrong kind of attribute"] + #[suggestion = "bar"] //~^ ERROR `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute sp: Span, } @@ -154,7 +154,7 @@ struct ErrorWithMessageAppliedToField { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct ErrorWithNonexistentField { - #[suggestion(message = "This is a suggestion", code = "{name}")] + #[suggestion(message = "bar", code = "{name}")] //~^ ERROR `name` doesn't refer to a field on this type suggestion: (Span, Applicability), } @@ -163,7 +163,7 @@ struct ErrorWithNonexistentField { //~^ ERROR invalid format string: expected `'}'` #[error(code = "E0123", slug = "foo")] struct ErrorMissingClosingBrace { - #[suggestion(message = "This is a suggestion", code = "{name")] + #[suggestion(message = "bar", code = "{name")] suggestion: (Span, Applicability), name: String, val: usize, @@ -173,7 +173,7 @@ struct ErrorMissingClosingBrace { //~^ ERROR invalid format string: unmatched `}` #[error(code = "E0123", slug = "foo")] struct ErrorMissingOpeningBrace { - #[suggestion(message = "This is a suggestion", code = "name}")] + #[suggestion(message = "bar", code = "name}")] suggestion: (Span, Applicability), name: String, val: usize, @@ -197,55 +197,54 @@ struct LabelOnNonSpan { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct Suggest { - #[suggestion(message = "This is a suggestion", code = "This is the suggested code")] - #[suggestion_short(message = "This is a suggestion", code = "This is the suggested code")] - #[suggestion_hidden(message = "This is a suggestion", code = "This is the suggested code")] - #[suggestion_verbose(message = "This is a suggestion", code = "This is the suggested code")] + #[suggestion(message = "bar", code = "This is the suggested code")] + #[suggestion_short(message = "qux", code = "This is the suggested code")] + #[suggestion_hidden(message = "foobar", code = "This is the suggested code")] + #[suggestion_verbose(message = "fooqux", code = "This is the suggested code")] suggestion: (Span, Applicability), } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct SuggestWithoutCode { - #[suggestion(message = "This is a suggestion")] + #[suggestion(message = "bar")] suggestion: (Span, Applicability), } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct SuggestWithBadKey { - #[suggestion(nonsense = "This is nonsense")] - //~^ ERROR `nonsense` is not a valid key for `#[suggestion(...)]` + #[suggestion(nonsense = "bar")] + //~^ ERROR `#[suggestion(nonsense = ...)]` is not a valid `SessionDiagnostic` field attribute suggestion: (Span, Applicability), } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct SuggestWithShorthandMsg { - #[suggestion(msg = "This is a suggestion")] - //~^ ERROR `msg` is not a valid key for `#[suggestion(...)]` + #[suggestion(msg = "bar")] + //~^ ERROR `#[suggestion(msg = ...)]` is not a valid `SessionDiagnostic` field attribute suggestion: (Span, Applicability), } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct SuggestWithoutMsg { - #[suggestion(code = "This is suggested code")] - //~^ ERROR missing suggestion message + #[suggestion(code = "bar")] suggestion: (Span, Applicability), } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct SuggestWithTypesSwapped { - #[suggestion(message = "This is a message", code = "This is suggested code")] + #[suggestion(message = "bar", code = "This is suggested code")] suggestion: (Applicability, Span), } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct SuggestWithWrongTypeApplicabilityOnly { - #[suggestion(message = "This is a message", code = "This is suggested code")] + #[suggestion(message = "bar", code = "This is suggested code")] //~^ ERROR wrong field type for suggestion suggestion: Applicability, } @@ -253,14 +252,14 @@ struct SuggestWithWrongTypeApplicabilityOnly { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct SuggestWithSpanOnly { - #[suggestion(message = "This is a message", code = "This is suggested code")] + #[suggestion(message = "bar", code = "This is suggested code")] suggestion: Span, } #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct SuggestWithDuplicateSpanAndApplicability { - #[suggestion(message = "This is a message", code = "This is suggested code")] + #[suggestion(message = "bar", code = "This is suggested code")] //~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one `Span` suggestion: (Span, Span, Applicability), } @@ -268,7 +267,7 @@ struct SuggestWithDuplicateSpanAndApplicability { #[derive(SessionDiagnostic)] #[error(code = "E0123", slug = "foo")] struct SuggestWithDuplicateApplicabilityAndSpan { - #[suggestion(message = "This is a message", code = "This is suggested code")] + #[suggestion(message = "bar", code = "This is suggested code")] //~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one suggestion: (Applicability, Applicability, Span), } @@ -277,7 +276,7 @@ struct SuggestWithDuplicateApplicabilityAndSpan { #[error(code = "E0123", slug = "foo")] struct WrongKindOfAnnotation { #[label("bar")] - //~^ ERROR invalid annotation list `#[label(...)]` + //~^ ERROR `#[label(...)]` is not a valid `SessionDiagnostic` field attribute z: Span, } @@ -286,7 +285,7 @@ struct WrongKindOfAnnotation { struct OptionsInErrors { #[label = "bar"] label: Option, - #[suggestion(message = "suggestion message")] + #[suggestion(message = "bar")] opt_sugg: Option<(Span, Applicability)>, } @@ -300,7 +299,7 @@ struct MoveOutOfBorrowError<'tcx> { span: Span, #[label = "qux"] other_span: Span, - #[suggestion(message = "consider cloning here", code = "{name}.clone()")] + #[suggestion(message = "bar", code = "{name}.clone()")] opt_sugg: Option<(Span, Applicability)>, } diff --git a/src/test/ui-fulldeps/session-derive-errors.stderr b/src/test/ui-fulldeps/session-derive-errors.stderr index dc8e807cea3..a528ae1607f 100644 --- a/src/test/ui-fulldeps/session-derive-errors.stderr +++ b/src/test/ui-fulldeps/session-derive-errors.stderr @@ -79,8 +79,8 @@ LL | #[error(nonsense = 4, code = "E0123", slug = "foo")] error: `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute --> $DIR/session-derive-errors.rs:86:5 | -LL | #[suggestion = "this is the wrong kind of attribute"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[suggestion = "bar"] + | ^^^^^^^^^^^^^^^^^^^^^ error: `error` specified multiple times --> $DIR/session-derive-errors.rs:93:1 @@ -166,10 +166,10 @@ LL | #[label = "bar"] | ^^^^^^^^^^^^^^^^ error: `name` doesn't refer to a field on this type - --> $DIR/session-derive-errors.rs:157:52 + --> $DIR/session-derive-errors.rs:157:42 | -LL | #[suggestion(message = "This is a suggestion", code = "{name}")] - | ^^^^^^^^^^^^^^^ +LL | #[suggestion(message = "bar", code = "{name}")] + | ^^^^^^^^ error: invalid format string: expected `'}'` but string was terminated --> $DIR/session-derive-errors.rs:162:16 @@ -197,30 +197,22 @@ error: the `#[label = ...]` attribute can only be applied to fields of type `Spa LL | #[label = "bar"] | ^^^^^^^^^^^^^^^^ -error: `nonsense` is not a valid key for `#[suggestion(...)]` +error: `#[suggestion(nonsense = ...)]` is not a valid `SessionDiagnostic` field attribute --> $DIR/session-derive-errors.rs:217:18 | -LL | #[suggestion(nonsense = "This is nonsense")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[suggestion(nonsense = "bar")] + | ^^^^^^^^^^^^^^^^ -error: `msg` is not a valid key for `#[suggestion(...)]` +error: `#[suggestion(msg = ...)]` is not a valid `SessionDiagnostic` field attribute --> $DIR/session-derive-errors.rs:225:18 | -LL | #[suggestion(msg = "This is a suggestion")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: missing suggestion message - --> $DIR/session-derive-errors.rs:233:7 - | -LL | #[suggestion(code = "This is suggested code")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: provide a suggestion message using `#[suggestion(message = "...")]` +LL | #[suggestion(msg = "bar")] + | ^^^^^^^^^^^ error: wrong field type for suggestion - --> $DIR/session-derive-errors.rs:248:5 + --> $DIR/session-derive-errors.rs:247:5 | -LL | / #[suggestion(message = "This is a message", code = "This is suggested code")] +LL | / #[suggestion(message = "bar", code = "This is suggested code")] LL | | LL | | suggestion: Applicability, | |_____________________________^ @@ -228,47 +220,47 @@ LL | | suggestion: Applicability, = help: `#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)` error: type of field annotated with `#[suggestion(...)]` contains more than one `Span` - --> $DIR/session-derive-errors.rs:263:5 + --> $DIR/session-derive-errors.rs:262:5 | -LL | / #[suggestion(message = "This is a message", code = "This is suggested code")] +LL | / #[suggestion(message = "bar", code = "This is suggested code")] LL | | LL | | suggestion: (Span, Span, Applicability), | |___________________________________________^ error: type of field annotated with `#[suggestion(...)]` contains more than one Applicability - --> $DIR/session-derive-errors.rs:271:5 + --> $DIR/session-derive-errors.rs:270:5 | -LL | / #[suggestion(message = "This is a message", code = "This is suggested code")] +LL | / #[suggestion(message = "bar", code = "This is suggested code")] LL | | LL | | suggestion: (Applicability, Applicability, Span), | |____________________________________________________^ -error: invalid annotation list `#[label(...)]` - --> $DIR/session-derive-errors.rs:279:7 +error: `#[label(...)]` is not a valid `SessionDiagnostic` field attribute + --> $DIR/session-derive-errors.rs:278:5 | LL | #[label("bar")] - | ^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ error: `#[help]` must come after `#[error(..)]` or `#[warn(..)]` - --> $DIR/session-derive-errors.rs:400:1 + --> $DIR/session-derive-errors.rs:399:1 | LL | #[help] | ^^^^^^^ error: `#[help = ...]` must come after `#[error(..)]` or `#[warn(..)]` - --> $DIR/session-derive-errors.rs:408:1 + --> $DIR/session-derive-errors.rs:407:1 | LL | #[help = "bar"] | ^^^^^^^^^^^^^^^ error: `#[note]` must come after `#[error(..)]` or `#[warn(..)]` - --> $DIR/session-derive-errors.rs:416:1 + --> $DIR/session-derive-errors.rs:415:1 | LL | #[note] | ^^^^^^^ error: `#[note = ...]` must come after `#[error(..)]` or `#[warn(..)]` - --> $DIR/session-derive-errors.rs:424:1 + --> $DIR/session-derive-errors.rs:423:1 | LL | #[note = "bar"] | ^^^^^^^^^^^^^^^ @@ -286,7 +278,7 @@ LL | #[nonsense] | ^^^^^^^^ error[E0599]: no method named `into_diagnostic_arg` found for struct `Hello` in the current scope - --> $DIR/session-derive-errors.rs:323:10 + --> $DIR/session-derive-errors.rs:322:10 | LL | struct Hello {} | ------------ method `into_diagnostic_arg` not found for this @@ -296,6 +288,6 @@ LL | #[derive(SessionDiagnostic)] | = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 38 previous errors +error: aborting due to 37 previous errors For more information about this error, try `rustc --explain E0599`.