Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up (sub)diagnostic derives #101851

Merged
merged 9 commits into from
Sep 26, 2022
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