macros: support translatable suggestions

Extends support for generating `DiagnosticMessage::FluentIdentifier`
messages from `SessionDiagnostic` derive to `#[suggestion]`.

Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
David Wood 2022-03-31 13:10:13 +01:00
parent b40ee88a28
commit 22685b9607
3 changed files with 141 additions and 123 deletions

View File

@ -594,6 +594,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
info: FieldInfo<'_>, info: FieldInfo<'_>,
) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> { ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
let diag = &self.diag; let diag = &self.diag;
let span = attr.span().unwrap();
let field_binding = &info.binding.binding; let field_binding = &info.binding.binding;
let name = attr.path.segments.last().unwrap().ident.to_string(); 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)) Ok(self.add_subdiagnostic(field_binding, name, name))
} }
other => throw_span_err!( other => throw_span_err!(
attr.span().unwrap(), span,
&format!("`#[{}]` is not a valid `SessionDiagnostic` field attribute", other) &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())) Ok(self.add_subdiagnostic(field_binding, name, &s.value()))
} }
other => throw_span_err!( other => throw_span_err!(
attr.span().unwrap(), span,
&format!( &format!(
"`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", "`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute",
other other
@ -636,77 +637,103 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
), ),
}, },
syn::Meta::NameValue(_) => throw_span_err!( syn::Meta::NameValue(_) => throw_span_err!(
attr.span().unwrap(), span,
&format!("`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", name), &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", name),
|diag| diag.help("value must be a string") |diag| diag.help("value must be a string")
), ),
syn::Meta::List(list) => { syn::Meta::List(syn::MetaList { path, nested, .. }) => {
match list.path.segments.iter().last().unwrap().ident.to_string().as_str() { let name = path.segments.last().unwrap().ident.to_string();
suggestion_kind @ "suggestion" let name = name.as_ref();
| suggestion_kind @ "suggestion_short"
| suggestion_kind @ "suggestion_hidden"
| suggestion_kind @ "suggestion_verbose" => {
let (span, applicability) = self.span_and_applicability_of_ty(info)?;
let mut msg = None; match name {
let mut code = None; "suggestion" | "suggestion_short" | "suggestion_hidden"
| "suggestion_verbose" => (),
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);
});
}
other => throw_span_err!( other => throw_span_err!(
list.span().unwrap(), span,
&format!("invalid annotation list `#[{}(...)]`", other) &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); })
} }
} }
} }

View File

@ -83,7 +83,7 @@ struct InvalidNestedStructAttr3 {}
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct WrongPlaceField { struct WrongPlaceField {
#[suggestion = "this is the wrong kind of attribute"] #[suggestion = "bar"]
//~^ ERROR `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute //~^ ERROR `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute
sp: Span, sp: Span,
} }
@ -154,7 +154,7 @@ struct ErrorWithMessageAppliedToField {
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct ErrorWithNonexistentField { 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 //~^ ERROR `name` doesn't refer to a field on this type
suggestion: (Span, Applicability), suggestion: (Span, Applicability),
} }
@ -163,7 +163,7 @@ struct ErrorWithNonexistentField {
//~^ ERROR invalid format string: expected `'}'` //~^ ERROR invalid format string: expected `'}'`
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct ErrorMissingClosingBrace { struct ErrorMissingClosingBrace {
#[suggestion(message = "This is a suggestion", code = "{name")] #[suggestion(message = "bar", code = "{name")]
suggestion: (Span, Applicability), suggestion: (Span, Applicability),
name: String, name: String,
val: usize, val: usize,
@ -173,7 +173,7 @@ struct ErrorMissingClosingBrace {
//~^ ERROR invalid format string: unmatched `}` //~^ ERROR invalid format string: unmatched `}`
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct ErrorMissingOpeningBrace { struct ErrorMissingOpeningBrace {
#[suggestion(message = "This is a suggestion", code = "name}")] #[suggestion(message = "bar", code = "name}")]
suggestion: (Span, Applicability), suggestion: (Span, Applicability),
name: String, name: String,
val: usize, val: usize,
@ -197,55 +197,54 @@ struct LabelOnNonSpan {
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct Suggest { struct Suggest {
#[suggestion(message = "This is a suggestion", code = "This is the suggested code")] #[suggestion(message = "bar", code = "This is the suggested code")]
#[suggestion_short(message = "This is a suggestion", code = "This is the suggested code")] #[suggestion_short(message = "qux", code = "This is the suggested code")]
#[suggestion_hidden(message = "This is a suggestion", code = "This is the suggested code")] #[suggestion_hidden(message = "foobar", code = "This is the suggested code")]
#[suggestion_verbose(message = "This is a suggestion", code = "This is the suggested code")] #[suggestion_verbose(message = "fooqux", code = "This is the suggested code")]
suggestion: (Span, Applicability), suggestion: (Span, Applicability),
} }
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct SuggestWithoutCode { struct SuggestWithoutCode {
#[suggestion(message = "This is a suggestion")] #[suggestion(message = "bar")]
suggestion: (Span, Applicability), suggestion: (Span, Applicability),
} }
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct SuggestWithBadKey { struct SuggestWithBadKey {
#[suggestion(nonsense = "This is nonsense")] #[suggestion(nonsense = "bar")]
//~^ ERROR `nonsense` is not a valid key for `#[suggestion(...)]` //~^ ERROR `#[suggestion(nonsense = ...)]` is not a valid `SessionDiagnostic` field attribute
suggestion: (Span, Applicability), suggestion: (Span, Applicability),
} }
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct SuggestWithShorthandMsg { struct SuggestWithShorthandMsg {
#[suggestion(msg = "This is a suggestion")] #[suggestion(msg = "bar")]
//~^ ERROR `msg` is not a valid key for `#[suggestion(...)]` //~^ ERROR `#[suggestion(msg = ...)]` is not a valid `SessionDiagnostic` field attribute
suggestion: (Span, Applicability), suggestion: (Span, Applicability),
} }
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct SuggestWithoutMsg { struct SuggestWithoutMsg {
#[suggestion(code = "This is suggested code")] #[suggestion(code = "bar")]
//~^ ERROR missing suggestion message
suggestion: (Span, Applicability), suggestion: (Span, Applicability),
} }
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct SuggestWithTypesSwapped { struct SuggestWithTypesSwapped {
#[suggestion(message = "This is a message", code = "This is suggested code")] #[suggestion(message = "bar", code = "This is suggested code")]
suggestion: (Applicability, Span), suggestion: (Applicability, Span),
} }
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct SuggestWithWrongTypeApplicabilityOnly { 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 //~^ ERROR wrong field type for suggestion
suggestion: Applicability, suggestion: Applicability,
} }
@ -253,14 +252,14 @@ struct SuggestWithWrongTypeApplicabilityOnly {
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct SuggestWithSpanOnly { struct SuggestWithSpanOnly {
#[suggestion(message = "This is a message", code = "This is suggested code")] #[suggestion(message = "bar", code = "This is suggested code")]
suggestion: Span, suggestion: Span,
} }
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct SuggestWithDuplicateSpanAndApplicability { 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` //~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one `Span`
suggestion: (Span, Span, Applicability), suggestion: (Span, Span, Applicability),
} }
@ -268,7 +267,7 @@ struct SuggestWithDuplicateSpanAndApplicability {
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct SuggestWithDuplicateApplicabilityAndSpan { 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 //~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one
suggestion: (Applicability, Applicability, Span), suggestion: (Applicability, Applicability, Span),
} }
@ -277,7 +276,7 @@ struct SuggestWithDuplicateApplicabilityAndSpan {
#[error(code = "E0123", slug = "foo")] #[error(code = "E0123", slug = "foo")]
struct WrongKindOfAnnotation { struct WrongKindOfAnnotation {
#[label("bar")] #[label("bar")]
//~^ ERROR invalid annotation list `#[label(...)]` //~^ ERROR `#[label(...)]` is not a valid `SessionDiagnostic` field attribute
z: Span, z: Span,
} }
@ -286,7 +285,7 @@ struct WrongKindOfAnnotation {
struct OptionsInErrors { struct OptionsInErrors {
#[label = "bar"] #[label = "bar"]
label: Option<Span>, label: Option<Span>,
#[suggestion(message = "suggestion message")] #[suggestion(message = "bar")]
opt_sugg: Option<(Span, Applicability)>, opt_sugg: Option<(Span, Applicability)>,
} }
@ -300,7 +299,7 @@ struct MoveOutOfBorrowError<'tcx> {
span: Span, span: Span,
#[label = "qux"] #[label = "qux"]
other_span: Span, other_span: Span,
#[suggestion(message = "consider cloning here", code = "{name}.clone()")] #[suggestion(message = "bar", code = "{name}.clone()")]
opt_sugg: Option<(Span, Applicability)>, opt_sugg: Option<(Span, Applicability)>,
} }

View File

@ -79,8 +79,8 @@ LL | #[error(nonsense = 4, code = "E0123", slug = "foo")]
error: `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute error: `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute
--> $DIR/session-derive-errors.rs:86:5 --> $DIR/session-derive-errors.rs:86:5
| |
LL | #[suggestion = "this is the wrong kind of attribute"] LL | #[suggestion = "bar"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^
error: `error` specified multiple times error: `error` specified multiple times
--> $DIR/session-derive-errors.rs:93:1 --> $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 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 error: invalid format string: expected `'}'` but string was terminated
--> $DIR/session-derive-errors.rs:162:16 --> $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"] 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 --> $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 --> $DIR/session-derive-errors.rs:225:18
| |
LL | #[suggestion(msg = "This is a suggestion")] LL | #[suggestion(msg = "bar")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^
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 = "...")]`
error: wrong field type for suggestion 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 | |
LL | | suggestion: Applicability, LL | | suggestion: Applicability,
| |_____________________________^ | |_____________________________^
@ -228,47 +220,47 @@ LL | | suggestion: Applicability,
= help: `#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, 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` 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 | |
LL | | suggestion: (Span, Span, Applicability), LL | | suggestion: (Span, Span, Applicability),
| |___________________________________________^ | |___________________________________________^
error: type of field annotated with `#[suggestion(...)]` contains more than one 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 | |
LL | | suggestion: (Applicability, Applicability, Span), LL | | suggestion: (Applicability, Applicability, Span),
| |____________________________________________________^ | |____________________________________________________^
error: invalid annotation list `#[label(...)]` error: `#[label(...)]` is not a valid `SessionDiagnostic` field attribute
--> $DIR/session-derive-errors.rs:279:7 --> $DIR/session-derive-errors.rs:278:5
| |
LL | #[label("bar")] LL | #[label("bar")]
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
error: `#[help]` must come after `#[error(..)]` or `#[warn(..)]` error: `#[help]` must come after `#[error(..)]` or `#[warn(..)]`
--> $DIR/session-derive-errors.rs:400:1 --> $DIR/session-derive-errors.rs:399:1
| |
LL | #[help] LL | #[help]
| ^^^^^^^ | ^^^^^^^
error: `#[help = ...]` must come after `#[error(..)]` or `#[warn(..)]` 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"] LL | #[help = "bar"]
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^
error: `#[note]` must come after `#[error(..)]` or `#[warn(..)]` error: `#[note]` must come after `#[error(..)]` or `#[warn(..)]`
--> $DIR/session-derive-errors.rs:416:1 --> $DIR/session-derive-errors.rs:415:1
| |
LL | #[note] LL | #[note]
| ^^^^^^^ | ^^^^^^^
error: `#[note = ...]` must come after `#[error(..)]` or `#[warn(..)]` 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"] 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 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 {} LL | struct Hello {}
| ------------ method `into_diagnostic_arg` not found for this | ------------ 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) = 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`. For more information about this error, try `rustc --explain E0599`.