Skip to content

Commit 1a93028

Browse files
authored
Rollup merge of rust-lang#101851 - Xiretza:diagnostic-derive-cleanups, r=davidtwco
Clean up (sub)diagnostic derives The biggest chunk of this is unifying the parsing of subdiagnostic attributes (`#[error]`, `#[suggestion(...)]`, `#[label(...)]`, etc) between `Subdiagnostic` and `Diagnostic` type attributes as well as `Diagnostic` field attributes. It also improves a number of proc macro diagnostics. Waiting for rust-lang#101558.
2 parents ff40f2e + 336a72a commit 1a93028

File tree

9 files changed

+912
-762
lines changed

9 files changed

+912
-762
lines changed

compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs

+165-323
Large diffs are not rendered by default.

compiler/rustc_macros/src/diagnostics/subdiagnostic.rs

+75-253
Large diffs are not rendered by default.

compiler/rustc_macros/src/diagnostics/utils.rs

+266-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError};
1+
use crate::diagnostics::error::{
2+
span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
3+
};
24
use proc_macro::Span;
35
use proc_macro2::TokenStream;
46
use quote::{format_ident, quote, ToTokens};
57
use std::collections::{BTreeSet, HashMap};
8+
use std::fmt;
69
use std::str::FromStr;
710
use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
11+
use syn::{MetaList, MetaNameValue, NestedMeta, Path};
812
use synstructure::{BindingInfo, Structure};
913

14+
use super::error::invalid_nested_attr;
15+
1016
/// Checks whether the type name of `ty` matches `name`.
1117
///
1218
/// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
@@ -172,13 +178,17 @@ pub(crate) struct FieldInfo<'a> {
172178
/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
173179
/// for error reporting if they are set more than once.
174180
pub(crate) trait SetOnce<T> {
175-
fn set_once(&mut self, _: (T, Span));
181+
fn set_once(&mut self, value: T, span: Span);
176182

177183
fn value(self) -> Option<T>;
184+
fn value_ref(&self) -> Option<&T>;
178185
}
179186

180-
impl<T> SetOnce<T> for Option<(T, Span)> {
181-
fn set_once(&mut self, (value, span): (T, Span)) {
187+
/// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`].
188+
pub(super) type SpannedOption<T> = Option<(T, Span)>;
189+
190+
impl<T> SetOnce<T> for SpannedOption<T> {
191+
fn set_once(&mut self, value: T, span: Span) {
182192
match self {
183193
None => {
184194
*self = Some((value, span));
@@ -194,6 +204,10 @@ impl<T> SetOnce<T> for Option<(T, Span)> {
194204
fn value(self) -> Option<T> {
195205
self.map(|(v, _)| v)
196206
}
207+
208+
fn value_ref(&self) -> Option<&T> {
209+
self.as_ref().map(|(v, _)| v)
210+
}
197211
}
198212

199213
pub(crate) trait HasFieldMap {
@@ -303,6 +317,7 @@ pub(crate) trait HasFieldMap {
303317

304318
/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
305319
/// the user's selection of applicability if specified in an attribute.
320+
#[derive(Clone, Copy)]
306321
pub(crate) enum Applicability {
307322
MachineApplicable,
308323
MaybeIncorrect,
@@ -359,3 +374,250 @@ pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<Stri
359374

360375
fields_map
361376
}
377+
378+
/// Possible styles for suggestion subdiagnostics.
379+
#[derive(Clone, Copy)]
380+
pub(super) enum SuggestionKind {
381+
/// `#[suggestion]`
382+
Normal,
383+
/// `#[suggestion_short]`
384+
Short,
385+
/// `#[suggestion_hidden]`
386+
Hidden,
387+
/// `#[suggestion_verbose]`
388+
Verbose,
389+
}
390+
391+
impl FromStr for SuggestionKind {
392+
type Err = ();
393+
394+
fn from_str(s: &str) -> Result<Self, Self::Err> {
395+
match s {
396+
"" => Ok(SuggestionKind::Normal),
397+
"_short" => Ok(SuggestionKind::Short),
398+
"_hidden" => Ok(SuggestionKind::Hidden),
399+
"_verbose" => Ok(SuggestionKind::Verbose),
400+
_ => Err(()),
401+
}
402+
}
403+
}
404+
405+
impl SuggestionKind {
406+
pub fn to_suggestion_style(&self) -> TokenStream {
407+
match self {
408+
SuggestionKind::Normal => {
409+
quote! { rustc_errors::SuggestionStyle::ShowCode }
410+
}
411+
SuggestionKind::Short => {
412+
quote! { rustc_errors::SuggestionStyle::HideCodeInline }
413+
}
414+
SuggestionKind::Hidden => {
415+
quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
416+
}
417+
SuggestionKind::Verbose => {
418+
quote! { rustc_errors::SuggestionStyle::ShowAlways }
419+
}
420+
}
421+
}
422+
}
423+
424+
/// Types of subdiagnostics that can be created using attributes
425+
#[derive(Clone)]
426+
pub(super) enum SubdiagnosticKind {
427+
/// `#[label(...)]`
428+
Label,
429+
/// `#[note(...)]`
430+
Note,
431+
/// `#[help(...)]`
432+
Help,
433+
/// `#[warning(...)]`
434+
Warn,
435+
/// `#[suggestion{,_short,_hidden,_verbose}]`
436+
Suggestion {
437+
suggestion_kind: SuggestionKind,
438+
applicability: SpannedOption<Applicability>,
439+
code: TokenStream,
440+
},
441+
/// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
442+
MultipartSuggestion {
443+
suggestion_kind: SuggestionKind,
444+
applicability: SpannedOption<Applicability>,
445+
},
446+
}
447+
448+
impl SubdiagnosticKind {
449+
/// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`,
450+
/// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the
451+
/// `SubdiagnosticKind` and the diagnostic slug, if specified.
452+
pub(super) fn from_attr(
453+
attr: &Attribute,
454+
fields: &impl HasFieldMap,
455+
) -> Result<(SubdiagnosticKind, Option<Path>), DiagnosticDeriveError> {
456+
let span = attr.span().unwrap();
457+
458+
let name = attr.path.segments.last().unwrap().ident.to_string();
459+
let name = name.as_str();
460+
461+
let meta = attr.parse_meta()?;
462+
let mut kind = match name {
463+
"label" => SubdiagnosticKind::Label,
464+
"note" => SubdiagnosticKind::Note,
465+
"help" => SubdiagnosticKind::Help,
466+
"warning" => SubdiagnosticKind::Warn,
467+
_ => {
468+
if let Some(suggestion_kind) =
469+
name.strip_prefix("suggestion").and_then(|s| s.parse().ok())
470+
{
471+
SubdiagnosticKind::Suggestion {
472+
suggestion_kind,
473+
applicability: None,
474+
code: TokenStream::new(),
475+
}
476+
} else if let Some(suggestion_kind) =
477+
name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
478+
{
479+
SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability: None }
480+
} else {
481+
throw_invalid_attr!(attr, &meta);
482+
}
483+
}
484+
};
485+
486+
let nested = match meta {
487+
Meta::List(MetaList { ref nested, .. }) => {
488+
// An attribute with properties, such as `#[suggestion(code = "...")]` or
489+
// `#[error(some::slug)]`
490+
nested
491+
}
492+
Meta::Path(_) => {
493+
// An attribute without a slug or other properties, such as `#[note]` - return
494+
// without further processing.
495+
//
496+
// Only allow this if there are no mandatory properties, such as `code = "..."` in
497+
// `#[suggestion(...)]`
498+
match kind {
499+
SubdiagnosticKind::Label
500+
| SubdiagnosticKind::Note
501+
| SubdiagnosticKind::Help
502+
| SubdiagnosticKind::Warn
503+
| SubdiagnosticKind::MultipartSuggestion { .. } => return Ok((kind, None)),
504+
SubdiagnosticKind::Suggestion { .. } => {
505+
throw_span_err!(span, "suggestion without `code = \"...\"`")
506+
}
507+
}
508+
}
509+
_ => {
510+
throw_invalid_attr!(attr, &meta)
511+
}
512+
};
513+
514+
let mut code = None;
515+
516+
let mut nested_iter = nested.into_iter().peekable();
517+
518+
// Peek at the first nested attribute: if it's a slug path, consume it.
519+
let slug = if let Some(NestedMeta::Meta(Meta::Path(path))) = nested_iter.peek() {
520+
let path = path.clone();
521+
// Advance the iterator.
522+
nested_iter.next();
523+
Some(path)
524+
} else {
525+
None
526+
};
527+
528+
for nested_attr in nested_iter {
529+
let meta = match nested_attr {
530+
NestedMeta::Meta(ref meta) => meta,
531+
NestedMeta::Lit(_) => {
532+
invalid_nested_attr(attr, &nested_attr).emit();
533+
continue;
534+
}
535+
};
536+
537+
let span = meta.span().unwrap();
538+
let nested_name = meta.path().segments.last().unwrap().ident.to_string();
539+
let nested_name = nested_name.as_str();
540+
541+
let value = match meta {
542+
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value,
543+
Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
544+
diag.help("a diagnostic slug must be the first argument to the attribute")
545+
}),
546+
_ => {
547+
invalid_nested_attr(attr, &nested_attr).emit();
548+
continue;
549+
}
550+
};
551+
552+
match (nested_name, &mut kind) {
553+
("code", SubdiagnosticKind::Suggestion { .. }) => {
554+
let formatted_str = fields.build_format(&value.value(), value.span());
555+
code.set_once(formatted_str, span);
556+
}
557+
(
558+
"applicability",
559+
SubdiagnosticKind::Suggestion { ref mut applicability, .. }
560+
| SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. },
561+
) => {
562+
let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
563+
span_err(span, "invalid applicability").emit();
564+
Applicability::Unspecified
565+
});
566+
applicability.set_once(value, span);
567+
}
568+
569+
// Invalid nested attribute
570+
(_, SubdiagnosticKind::Suggestion { .. }) => {
571+
invalid_nested_attr(attr, &nested_attr)
572+
.help("only `code` and `applicability` are valid nested attributes")
573+
.emit();
574+
}
575+
(_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
576+
invalid_nested_attr(attr, &nested_attr)
577+
.help("only `applicability` is a valid nested attributes")
578+
.emit()
579+
}
580+
_ => {
581+
invalid_nested_attr(attr, &nested_attr).emit();
582+
}
583+
}
584+
}
585+
586+
match kind {
587+
SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
588+
*code_field = if let Some((code, _)) = code {
589+
code
590+
} else {
591+
span_err(span, "suggestion without `code = \"...\"`").emit();
592+
quote! { "" }
593+
}
594+
}
595+
SubdiagnosticKind::Label
596+
| SubdiagnosticKind::Note
597+
| SubdiagnosticKind::Help
598+
| SubdiagnosticKind::Warn
599+
| SubdiagnosticKind::MultipartSuggestion { .. } => {}
600+
}
601+
602+
Ok((kind, slug))
603+
}
604+
}
605+
606+
impl quote::IdentFragment for SubdiagnosticKind {
607+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608+
match self {
609+
SubdiagnosticKind::Label => write!(f, "label"),
610+
SubdiagnosticKind::Note => write!(f, "note"),
611+
SubdiagnosticKind::Help => write!(f, "help"),
612+
SubdiagnosticKind::Warn => write!(f, "warn"),
613+
SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"),
614+
SubdiagnosticKind::MultipartSuggestion { .. } => {
615+
write!(f, "multipart_suggestion_with_style")
616+
}
617+
}
618+
}
619+
620+
fn span(&self) -> Option<proc_macro2::Span> {
621+
None
622+
}
623+
}

compiler/rustc_parse/src/parser/diagnostics.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ pub enum BadTypePlusSub {
289289
#[diag(parser::maybe_recover_from_bad_qpath_stage_2)]
290290
struct BadQPathStage2 {
291291
#[primary_span]
292-
#[suggestion(applicability = "maybe-incorrect")]
292+
#[suggestion(code = "", applicability = "maybe-incorrect")]
293293
span: Span,
294294
ty: String,
295295
}
@@ -298,7 +298,7 @@ struct BadQPathStage2 {
298298
#[diag(parser::incorrect_semicolon)]
299299
struct IncorrectSemicolon<'a> {
300300
#[primary_span]
301-
#[suggestion_short(applicability = "machine-applicable")]
301+
#[suggestion_short(code = "", applicability = "machine-applicable")]
302302
span: Span,
303303
#[help]
304304
opt_help: Option<()>,
@@ -309,7 +309,7 @@ struct IncorrectSemicolon<'a> {
309309
#[diag(parser::incorrect_use_of_await)]
310310
struct IncorrectUseOfAwait {
311311
#[primary_span]
312-
#[suggestion(parser::parentheses_suggestion, applicability = "machine-applicable")]
312+
#[suggestion(parser::parentheses_suggestion, code = "", applicability = "machine-applicable")]
313313
span: Span,
314314
}
315315

@@ -329,7 +329,7 @@ struct IncorrectAwait {
329329
struct InInTypo {
330330
#[primary_span]
331331
span: Span,
332-
#[suggestion(applicability = "machine-applicable")]
332+
#[suggestion(code = "", applicability = "machine-applicable")]
333333
sugg_span: Span,
334334
}
335335

compiler/rustc_passes/src/errors.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ pub struct LinkSection {
462462
pub struct NoMangleForeign {
463463
#[label]
464464
pub span: Span,
465-
#[suggestion(applicability = "machine-applicable")]
465+
#[suggestion(code = "", applicability = "machine-applicable")]
466466
pub attr_span: Span,
467467
pub foreign_item_kind: &'static str,
468468
}
@@ -596,7 +596,7 @@ pub enum UnusedNote {
596596
#[derive(LintDiagnostic)]
597597
#[diag(passes::unused)]
598598
pub struct Unused {
599-
#[suggestion(applicability = "machine-applicable")]
599+
#[suggestion(code = "", applicability = "machine-applicable")]
600600
pub attr_span: Span,
601601
#[subdiagnostic]
602602
pub note: UnusedNote,

0 commit comments

Comments
 (0)