Skip to content

Commit

Permalink
Rollup merge of rust-lang#101851 - Xiretza:diagnostic-derive-cleanups…
Browse files Browse the repository at this point in the history
…, 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.
  • Loading branch information
fee1-dead authored Sep 26, 2022
2 parents ff40f2e + 336a72a commit 1a93028
Show file tree
Hide file tree
Showing 9 changed files with 912 additions and 762 deletions.
488 changes: 165 additions & 323 deletions compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs

Large diffs are not rendered by default.

328 changes: 75 additions & 253 deletions compiler/rustc_macros/src/diagnostics/subdiagnostic.rs

Large diffs are not rendered by default.

270 changes: 266 additions & 4 deletions compiler/rustc_macros/src/diagnostics/utils.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError};
use crate::diagnostics::error::{
span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
};
use proc_macro::Span;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use std::collections::{BTreeSet, HashMap};
use std::fmt;
use std::str::FromStr;
use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
use syn::{MetaList, MetaNameValue, NestedMeta, Path};
use synstructure::{BindingInfo, Structure};

use super::error::invalid_nested_attr;

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

fn value(self) -> Option<T>;
fn value_ref(&self) -> Option<&T>;
}

impl<T> SetOnce<T> for Option<(T, Span)> {
fn set_once(&mut self, (value, span): (T, Span)) {
/// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`].
pub(super) type SpannedOption<T> = Option<(T, Span)>;

impl<T> SetOnce<T> for SpannedOption<T> {
fn set_once(&mut self, value: T, span: Span) {
match self {
None => {
*self = Some((value, span));
Expand All @@ -194,6 +204,10 @@ impl<T> SetOnce<T> for Option<(T, Span)> {
fn value(self) -> Option<T> {
self.map(|(v, _)| v)
}

fn value_ref(&self) -> Option<&T> {
self.as_ref().map(|(v, _)| v)
}
}

pub(crate) trait HasFieldMap {
Expand Down Expand Up @@ -303,6 +317,7 @@ pub(crate) trait HasFieldMap {

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

fields_map
}

/// Possible styles for suggestion subdiagnostics.
#[derive(Clone, Copy)]
pub(super) enum SuggestionKind {
/// `#[suggestion]`
Normal,
/// `#[suggestion_short]`
Short,
/// `#[suggestion_hidden]`
Hidden,
/// `#[suggestion_verbose]`
Verbose,
}

impl FromStr for SuggestionKind {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"" => Ok(SuggestionKind::Normal),
"_short" => Ok(SuggestionKind::Short),
"_hidden" => Ok(SuggestionKind::Hidden),
"_verbose" => Ok(SuggestionKind::Verbose),
_ => Err(()),
}
}
}

impl SuggestionKind {
pub fn to_suggestion_style(&self) -> TokenStream {
match self {
SuggestionKind::Normal => {
quote! { rustc_errors::SuggestionStyle::ShowCode }
}
SuggestionKind::Short => {
quote! { rustc_errors::SuggestionStyle::HideCodeInline }
}
SuggestionKind::Hidden => {
quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
}
SuggestionKind::Verbose => {
quote! { rustc_errors::SuggestionStyle::ShowAlways }
}
}
}
}

/// Types of subdiagnostics that can be created using attributes
#[derive(Clone)]
pub(super) enum SubdiagnosticKind {
/// `#[label(...)]`
Label,
/// `#[note(...)]`
Note,
/// `#[help(...)]`
Help,
/// `#[warning(...)]`
Warn,
/// `#[suggestion{,_short,_hidden,_verbose}]`
Suggestion {
suggestion_kind: SuggestionKind,
applicability: SpannedOption<Applicability>,
code: TokenStream,
},
/// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
MultipartSuggestion {
suggestion_kind: SuggestionKind,
applicability: SpannedOption<Applicability>,
},
}

impl SubdiagnosticKind {
/// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`,
/// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the
/// `SubdiagnosticKind` and the diagnostic slug, if specified.
pub(super) fn from_attr(
attr: &Attribute,
fields: &impl HasFieldMap,
) -> Result<(SubdiagnosticKind, Option<Path>), DiagnosticDeriveError> {
let span = attr.span().unwrap();

let name = attr.path.segments.last().unwrap().ident.to_string();
let name = name.as_str();

let meta = attr.parse_meta()?;
let mut kind = match name {
"label" => SubdiagnosticKind::Label,
"note" => SubdiagnosticKind::Note,
"help" => SubdiagnosticKind::Help,
"warning" => SubdiagnosticKind::Warn,
_ => {
if let Some(suggestion_kind) =
name.strip_prefix("suggestion").and_then(|s| s.parse().ok())
{
SubdiagnosticKind::Suggestion {
suggestion_kind,
applicability: None,
code: TokenStream::new(),
}
} else if let Some(suggestion_kind) =
name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
{
SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability: None }
} else {
throw_invalid_attr!(attr, &meta);
}
}
};

let nested = match meta {
Meta::List(MetaList { ref nested, .. }) => {
// An attribute with properties, such as `#[suggestion(code = "...")]` or
// `#[error(some::slug)]`
nested
}
Meta::Path(_) => {
// An attribute without a slug or other properties, such as `#[note]` - return
// without further processing.
//
// Only allow this if there are no mandatory properties, such as `code = "..."` in
// `#[suggestion(...)]`
match kind {
SubdiagnosticKind::Label
| SubdiagnosticKind::Note
| SubdiagnosticKind::Help
| SubdiagnosticKind::Warn
| SubdiagnosticKind::MultipartSuggestion { .. } => return Ok((kind, None)),
SubdiagnosticKind::Suggestion { .. } => {
throw_span_err!(span, "suggestion without `code = \"...\"`")
}
}
}
_ => {
throw_invalid_attr!(attr, &meta)
}
};

let mut code = None;

let mut nested_iter = nested.into_iter().peekable();

// Peek at the first nested attribute: if it's a slug path, consume it.
let slug = if let Some(NestedMeta::Meta(Meta::Path(path))) = nested_iter.peek() {
let path = path.clone();
// Advance the iterator.
nested_iter.next();
Some(path)
} else {
None
};

for nested_attr in nested_iter {
let meta = match nested_attr {
NestedMeta::Meta(ref meta) => meta,
NestedMeta::Lit(_) => {
invalid_nested_attr(attr, &nested_attr).emit();
continue;
}
};

let span = meta.span().unwrap();
let nested_name = meta.path().segments.last().unwrap().ident.to_string();
let nested_name = nested_name.as_str();

let value = match meta {
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value,
Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
diag.help("a diagnostic slug must be the first argument to the attribute")
}),
_ => {
invalid_nested_attr(attr, &nested_attr).emit();
continue;
}
};

match (nested_name, &mut kind) {
("code", SubdiagnosticKind::Suggestion { .. }) => {
let formatted_str = fields.build_format(&value.value(), value.span());
code.set_once(formatted_str, span);
}
(
"applicability",
SubdiagnosticKind::Suggestion { ref mut applicability, .. }
| SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. },
) => {
let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
span_err(span, "invalid applicability").emit();
Applicability::Unspecified
});
applicability.set_once(value, span);
}

// Invalid nested attribute
(_, SubdiagnosticKind::Suggestion { .. }) => {
invalid_nested_attr(attr, &nested_attr)
.help("only `code` and `applicability` are valid nested attributes")
.emit();
}
(_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
invalid_nested_attr(attr, &nested_attr)
.help("only `applicability` is a valid nested attributes")
.emit()
}
_ => {
invalid_nested_attr(attr, &nested_attr).emit();
}
}
}

match kind {
SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
*code_field = if let Some((code, _)) = code {
code
} else {
span_err(span, "suggestion without `code = \"...\"`").emit();
quote! { "" }
}
}
SubdiagnosticKind::Label
| SubdiagnosticKind::Note
| SubdiagnosticKind::Help
| SubdiagnosticKind::Warn
| SubdiagnosticKind::MultipartSuggestion { .. } => {}
}

Ok((kind, slug))
}
}

impl quote::IdentFragment for SubdiagnosticKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SubdiagnosticKind::Label => write!(f, "label"),
SubdiagnosticKind::Note => write!(f, "note"),
SubdiagnosticKind::Help => write!(f, "help"),
SubdiagnosticKind::Warn => write!(f, "warn"),
SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"),
SubdiagnosticKind::MultipartSuggestion { .. } => {
write!(f, "multipart_suggestion_with_style")
}
}
}

fn span(&self) -> Option<proc_macro2::Span> {
None
}
}
8 changes: 4 additions & 4 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ pub enum BadTypePlusSub {
#[diag(parser::maybe_recover_from_bad_qpath_stage_2)]
struct BadQPathStage2 {
#[primary_span]
#[suggestion(applicability = "maybe-incorrect")]
#[suggestion(code = "", applicability = "maybe-incorrect")]
span: Span,
ty: String,
}
Expand All @@ -298,7 +298,7 @@ struct BadQPathStage2 {
#[diag(parser::incorrect_semicolon)]
struct IncorrectSemicolon<'a> {
#[primary_span]
#[suggestion_short(applicability = "machine-applicable")]
#[suggestion_short(code = "", applicability = "machine-applicable")]
span: Span,
#[help]
opt_help: Option<()>,
Expand All @@ -309,7 +309,7 @@ struct IncorrectSemicolon<'a> {
#[diag(parser::incorrect_use_of_await)]
struct IncorrectUseOfAwait {
#[primary_span]
#[suggestion(parser::parentheses_suggestion, applicability = "machine-applicable")]
#[suggestion(parser::parentheses_suggestion, code = "", applicability = "machine-applicable")]
span: Span,
}

Expand All @@ -329,7 +329,7 @@ struct IncorrectAwait {
struct InInTypo {
#[primary_span]
span: Span,
#[suggestion(applicability = "machine-applicable")]
#[suggestion(code = "", applicability = "machine-applicable")]
sugg_span: Span,
}

Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ pub struct LinkSection {
pub struct NoMangleForeign {
#[label]
pub span: Span,
#[suggestion(applicability = "machine-applicable")]
#[suggestion(code = "", applicability = "machine-applicable")]
pub attr_span: Span,
pub foreign_item_kind: &'static str,
}
Expand Down Expand Up @@ -596,7 +596,7 @@ pub enum UnusedNote {
#[derive(LintDiagnostic)]
#[diag(passes::unused)]
pub struct Unused {
#[suggestion(applicability = "machine-applicable")]
#[suggestion(code = "", applicability = "machine-applicable")]
pub attr_span: Span,
#[subdiagnostic]
pub note: UnusedNote,
Expand Down
Loading

0 comments on commit 1a93028

Please sign in to comment.