Skip to content

Commit e7251cc

Browse files
committed
Extract subdiagnostic attribute parsing
1 parent d4a1a6f commit e7251cc

File tree

4 files changed

+402
-315
lines changed

4 files changed

+402
-315
lines changed

compiler/rustc_macros/src/diagnostics/subdiagnostic.rs

+42-231
Original file line numberDiff line numberDiff line change
@@ -4,100 +4,16 @@ use crate::diagnostics::error::{
44
span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
55
};
66
use crate::diagnostics::utils::{
7-
report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
8-
Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
7+
report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span, FieldInfo,
8+
FieldInnerTy, HasFieldMap, SetOnce,
99
};
1010
use proc_macro2::TokenStream;
1111
use quote::{format_ident, quote};
1212
use std::collections::HashMap;
13-
use std::fmt;
14-
use std::str::FromStr;
1513
use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path};
1614
use synstructure::{BindingInfo, Structure, VariantInfo};
1715

18-
use super::utils::SpannedOption;
19-
20-
/// Which kind of suggestion is being created?
21-
#[derive(Clone, Copy)]
22-
enum SubdiagnosticSuggestionKind {
23-
/// `#[suggestion]`
24-
Normal,
25-
/// `#[suggestion_short]`
26-
Short,
27-
/// `#[suggestion_hidden]`
28-
Hidden,
29-
/// `#[suggestion_verbose]`
30-
Verbose,
31-
}
32-
33-
impl FromStr for SubdiagnosticSuggestionKind {
34-
type Err = ();
35-
36-
fn from_str(s: &str) -> Result<Self, Self::Err> {
37-
match s {
38-
"" => Ok(SubdiagnosticSuggestionKind::Normal),
39-
"_short" => Ok(SubdiagnosticSuggestionKind::Short),
40-
"_hidden" => Ok(SubdiagnosticSuggestionKind::Hidden),
41-
"_verbose" => Ok(SubdiagnosticSuggestionKind::Verbose),
42-
_ => Err(()),
43-
}
44-
}
45-
}
46-
47-
impl SubdiagnosticSuggestionKind {
48-
pub fn to_suggestion_style(&self) -> TokenStream {
49-
match self {
50-
SubdiagnosticSuggestionKind::Normal => {
51-
quote! { rustc_errors::SuggestionStyle::ShowCode }
52-
}
53-
SubdiagnosticSuggestionKind::Short => {
54-
quote! { rustc_errors::SuggestionStyle::HideCodeInline }
55-
}
56-
SubdiagnosticSuggestionKind::Hidden => {
57-
quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
58-
}
59-
SubdiagnosticSuggestionKind::Verbose => {
60-
quote! { rustc_errors::SuggestionStyle::ShowAlways }
61-
}
62-
}
63-
}
64-
}
65-
66-
/// Which kind of subdiagnostic is being created from a variant?
67-
#[derive(Clone)]
68-
enum SubdiagnosticKind {
69-
/// `#[label(...)]`
70-
Label,
71-
/// `#[note(...)]`
72-
Note,
73-
/// `#[help(...)]`
74-
Help,
75-
/// `#[warning(...)]`
76-
Warn,
77-
/// `#[suggestion{,_short,_hidden,_verbose}]`
78-
Suggestion { suggestion_kind: SubdiagnosticSuggestionKind, code: TokenStream },
79-
/// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
80-
MultipartSuggestion { suggestion_kind: SubdiagnosticSuggestionKind },
81-
}
82-
83-
impl quote::IdentFragment for SubdiagnosticKind {
84-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85-
match self {
86-
SubdiagnosticKind::Label => write!(f, "label"),
87-
SubdiagnosticKind::Note => write!(f, "note"),
88-
SubdiagnosticKind::Help => write!(f, "help"),
89-
SubdiagnosticKind::Warn => write!(f, "warn"),
90-
SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"),
91-
SubdiagnosticKind::MultipartSuggestion { .. } => {
92-
write!(f, "multipart_suggestion_with_style")
93-
}
94-
}
95-
}
96-
97-
fn span(&self) -> Option<proc_macro2::Span> {
98-
None
99-
}
100-
}
16+
use super::utils::{SpannedOption, SubdiagnosticKind};
10117

10218
/// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
10319
pub(crate) struct SubdiagnosticDerive<'a> {
@@ -198,8 +114,8 @@ struct SubdiagnosticDeriveBuilder<'a> {
198114

199115
/// Identifier for the binding to the `#[primary_span]` field.
200116
span_field: SpannedOption<proc_macro2::Ident>,
201-
/// If a suggestion, the identifier for the binding to the `#[applicability]` field or a
202-
/// `rustc_errors::Applicability::*` variant directly.
117+
118+
/// The binding to the `#[applicability]` field, if present.
203119
applicability: SpannedOption<TokenStream>,
204120

205121
/// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error
@@ -219,6 +135,7 @@ struct KindsStatistics {
219135
has_multipart_suggestion: bool,
220136
all_multipart_suggestions: bool,
221137
has_normal_suggestion: bool,
138+
all_applicabilities_static: bool,
222139
}
223140

224141
impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
@@ -227,8 +144,15 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
227144
has_multipart_suggestion: false,
228145
all_multipart_suggestions: true,
229146
has_normal_suggestion: false,
147+
all_applicabilities_static: true,
230148
};
149+
231150
for kind in kinds {
151+
if let SubdiagnosticKind::MultipartSuggestion { applicability: None, .. }
152+
| SubdiagnosticKind::Suggestion { applicability: None, .. } = kind
153+
{
154+
ret.all_applicabilities_static = false;
155+
}
232156
if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
233157
ret.has_multipart_suggestion = true;
234158
} else {
@@ -248,151 +172,22 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
248172
let mut kind_slugs = vec![];
249173

250174
for attr in self.variant.ast().attrs {
251-
let span = attr.span().unwrap();
252-
253-
let name = attr.path.segments.last().unwrap().ident.to_string();
254-
let name = name.as_str();
255-
256-
let meta = attr.parse_meta()?;
257-
let Meta::List(MetaList { ref nested, .. }) = meta else {
258-
throw_invalid_attr!(attr, &meta);
259-
};
260-
261-
let mut kind = match name {
262-
"label" => SubdiagnosticKind::Label,
263-
"note" => SubdiagnosticKind::Note,
264-
"help" => SubdiagnosticKind::Help,
265-
"warning" => SubdiagnosticKind::Warn,
266-
_ => {
267-
if let Some(suggestion_kind) =
268-
name.strip_prefix("suggestion").and_then(|s| s.parse().ok())
269-
{
270-
SubdiagnosticKind::Suggestion { suggestion_kind, code: TokenStream::new() }
271-
} else if let Some(suggestion_kind) =
272-
name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
273-
{
274-
SubdiagnosticKind::MultipartSuggestion { suggestion_kind }
275-
} else {
276-
throw_invalid_attr!(attr, &meta);
277-
}
278-
}
279-
};
280-
281-
let mut slug = None;
282-
let mut code = None;
283-
284-
let mut nested_iter = nested.into_iter();
285-
if let Some(nested_attr) = nested_iter.next() {
286-
match nested_attr {
287-
NestedMeta::Meta(Meta::Path(path)) => {
288-
slug.set_once(path.clone(), span);
289-
}
290-
NestedMeta::Meta(meta @ Meta::NameValue(_))
291-
if matches!(
292-
meta.path().segments.last().unwrap().ident.to_string().as_str(),
293-
"code" | "applicability"
294-
) =>
295-
{
296-
// Don't error for valid follow-up attributes.
297-
}
298-
nested_attr => {
299-
throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
300-
diag.help(
301-
"first argument of the attribute should be the diagnostic \
302-
slug",
303-
)
304-
})
305-
}
306-
};
307-
}
175+
let (kind, slug) = SubdiagnosticKind::from_attr(attr, self)?;
308176

309-
for nested_attr in nested_iter {
310-
let meta = match nested_attr {
311-
NestedMeta::Meta(ref meta) => meta,
312-
_ => throw_invalid_nested_attr!(attr, &nested_attr),
313-
};
314-
315-
let span = meta.span().unwrap();
316-
let nested_name = meta.path().segments.last().unwrap().ident.to_string();
317-
let nested_name = nested_name.as_str();
318-
319-
let value = match meta {
320-
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value,
321-
Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
322-
diag.help("a diagnostic slug must be the first argument to the attribute")
323-
}),
324-
_ => throw_invalid_nested_attr!(attr, &nested_attr),
325-
};
326-
327-
match nested_name {
328-
"code" => {
329-
if matches!(kind, SubdiagnosticKind::Suggestion { .. }) {
330-
let formatted_str = self.build_format(&value.value(), value.span());
331-
code.set_once(formatted_str, span);
332-
} else {
333-
span_err(
334-
span,
335-
&format!(
336-
"`code` is not a valid nested attribute of a `{}` attribute",
337-
name
338-
),
339-
)
340-
.emit();
341-
}
342-
}
343-
"applicability" => {
344-
if matches!(
345-
kind,
346-
SubdiagnosticKind::Suggestion { .. }
347-
| SubdiagnosticKind::MultipartSuggestion { .. }
348-
) {
349-
let value =
350-
Applicability::from_str(&value.value()).unwrap_or_else(|()| {
351-
span_err(span, "invalid applicability").emit();
352-
Applicability::Unspecified
353-
});
354-
self.applicability.set_once(quote! { #value }, span);
355-
} else {
356-
span_err(
357-
span,
358-
&format!(
359-
"`applicability` is not a valid nested attribute of a `{}` attribute",
360-
name
361-
)
362-
).emit();
363-
}
364-
}
365-
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
366-
diag.help("only `code` and `applicability` are valid nested attributes")
367-
}),
368-
}
369-
}
177+
let Some(slug) = slug else {
178+
let name = attr.path.segments.last().unwrap().ident.to_string();
179+
let name = name.as_str();
370180

371-
let Some((slug, _)) = slug else {
372181
throw_span_err!(
373-
span,
182+
attr.span().unwrap(),
374183
&format!(
375184
"diagnostic slug must be first argument of a `#[{}(...)]` attribute",
376185
name
377186
)
378187
);
379188
};
380189

381-
match kind {
382-
SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
383-
let Some((code, _)) = code else {
384-
throw_span_err!(span, "suggestion without `code = \"...\"`");
385-
};
386-
*code_field = code;
387-
}
388-
SubdiagnosticKind::Label
389-
| SubdiagnosticKind::Note
390-
| SubdiagnosticKind::Help
391-
| SubdiagnosticKind::Warn
392-
| SubdiagnosticKind::MultipartSuggestion { .. } => {}
393-
}
394-
395-
kind_slugs.push((kind, slug))
190+
kind_slugs.push((kind, slug));
396191
}
397192

398193
Ok(kind_slugs)
@@ -510,6 +305,15 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
510305
if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion {
511306
report_error_if_not_applied_to_applicability(attr, &info)?;
512307

308+
if kind_stats.all_applicabilities_static {
309+
span_err(
310+
span,
311+
"`#[applicability]` has no effect if all `#[suggestion]`/\
312+
`#[multipart_suggestion]` attributes have a static \
313+
`applicability = \"...\"`",
314+
)
315+
.emit();
316+
}
513317
let binding = info.binding.binding.clone();
514318
self.applicability.set_once(quote! { #binding }, span);
515319
} else {
@@ -638,19 +442,20 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
638442
.collect();
639443

640444
let span_field = self.span_field.value_ref();
641-
let applicability = self
642-
.applicability
643-
.take()
644-
.value()
645-
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
646445

647446
let diag = &self.diag;
648447
let mut calls = TokenStream::new();
649448
for (kind, slug) in kind_slugs {
650449
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
651450
let message = quote! { rustc_errors::fluent::#slug };
652451
let call = match kind {
653-
SubdiagnosticKind::Suggestion { suggestion_kind, code } => {
452+
SubdiagnosticKind::Suggestion { suggestion_kind, applicability, code } => {
453+
let applicability = applicability
454+
.value()
455+
.map(|a| quote! { #a })
456+
.or_else(|| self.applicability.take().value())
457+
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
458+
654459
if let Some(span) = span_field {
655460
let style = suggestion_kind.to_suggestion_style();
656461

@@ -660,7 +465,13 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
660465
quote! { unreachable!(); }
661466
}
662467
}
663-
SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => {
468+
SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability } => {
469+
let applicability = applicability
470+
.value()
471+
.map(|a| quote! { #a })
472+
.or_else(|| self.applicability.take().value())
473+
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
474+
664475
if !self.has_suggestion_parts {
665476
span_err(
666477
self.span,

0 commit comments

Comments
 (0)