macros: support doc comments in diag derives

Documentation comments shouldn't affect the diagnostic derive in any
way, but explicit support has to be added for ignoring the `doc`
attribute.

Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
David Wood 2022-10-14 11:00:46 +01:00
parent 1536ab1b38
commit 7fbaf27696
5 changed files with 90 additions and 14 deletions

View File

@ -5,7 +5,7 @@ use crate::diagnostics::error::{
DiagnosticDeriveError,
};
use crate::diagnostics::utils::{
build_field_mapping, report_error_if_not_applied_to_span, report_type_error,
build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap,
HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
};
@ -152,8 +152,12 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
fn parse_subdiag_attribute(
&self,
attr: &Attribute,
) -> Result<(SubdiagnosticKind, Path), DiagnosticDeriveError> {
let (subdiag, slug) = SubdiagnosticKind::from_attr(attr, self)?;
) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
let Some((subdiag, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
return Ok(None);
};
if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag {
let meta = attr.parse_meta()?;
@ -170,7 +174,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
});
Ok((subdiag, slug))
Ok(Some((subdiag, slug)))
}
/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
@ -182,6 +186,11 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
) -> Result<TokenStream, DiagnosticDeriveError> {
let diag = &self.parent.diag;
// Always allow documentation comments.
if is_doc_comment(attr) {
return Ok(quote! {});
}
let name = attr.path.segments.last().unwrap().ident.to_string();
let name = name.as_str();
let meta = attr.parse_meta()?;
@ -250,7 +259,11 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
return Ok(tokens);
}
let (subdiag, slug) = self.parse_subdiag_attribute(attr)?;
let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
return Ok(quote! {});
};
let fn_ident = format_ident!("{}", subdiag);
match subdiag {
SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
@ -291,6 +304,11 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
.attrs
.iter()
.map(move |attr| {
// Always allow documentation comments.
if is_doc_comment(attr) {
return quote! {};
}
let name = attr.path.segments.last().unwrap().ident.to_string();
let needs_clone =
name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_));
@ -397,8 +415,11 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
_ => (),
}
let (subdiag, slug) = self.parse_subdiag_attribute(attr)?;
let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
return Ok(quote! {});
};
let fn_ident = format_ident!("{}", subdiag);
match subdiag {
SubdiagnosticKind::Label => {

View File

@ -5,9 +5,9 @@ use crate::diagnostics::error::{
DiagnosticDeriveError,
};
use crate::diagnostics::utils::{
build_field_mapping, new_code_ident, report_error_if_not_applied_to_applicability,
report_error_if_not_applied_to_span, FieldInfo, FieldInnerTy, FieldMap, HasFieldMap, SetOnce,
SpannedOption, SubdiagnosticKind,
build_field_mapping, is_doc_comment, new_code_ident,
report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span, FieldInfo,
FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
@ -43,6 +43,11 @@ impl SubdiagnosticDeriveBuilder {
if matches!(ast.data, syn::Data::Enum(..)) {
for attr in &ast.attrs {
// Always allow documentation comments.
if is_doc_comment(attr) {
continue;
}
span_err(
attr.span().unwrap(),
"unsupported type attribute for subdiagnostic enum",
@ -173,7 +178,11 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
let mut kind_slugs = vec![];
for attr in self.variant.ast().attrs {
let (kind, slug) = SubdiagnosticKind::from_attr(attr, self)?;
let Some((kind, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
continue;
};
let Some(slug) = slug else {
let name = attr.path.segments.last().unwrap().ident.to_string();
@ -227,6 +236,11 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
ast.attrs
.iter()
.map(|attr| {
// Always allow documentation comments.
if is_doc_comment(attr) {
return quote! {};
}
let info = FieldInfo {
binding,
ty: inner_ty.inner_type().unwrap_or(&ast.ty),

View File

@ -477,7 +477,12 @@ impl SubdiagnosticKind {
pub(super) fn from_attr(
attr: &Attribute,
fields: &impl HasFieldMap,
) -> Result<(SubdiagnosticKind, Option<Path>), DiagnosticDeriveError> {
) -> Result<Option<(SubdiagnosticKind, Option<Path>)>, DiagnosticDeriveError> {
// Always allow documentation comments.
if is_doc_comment(attr) {
return Ok(None);
}
let span = attr.span().unwrap();
let name = attr.path.segments.last().unwrap().ident.to_string();
@ -526,7 +531,9 @@ impl SubdiagnosticKind {
| SubdiagnosticKind::Note
| SubdiagnosticKind::Help
| SubdiagnosticKind::Warn
| SubdiagnosticKind::MultipartSuggestion { .. } => return Ok((kind, None)),
| SubdiagnosticKind::MultipartSuggestion { .. } => {
return Ok(Some((kind, None)));
}
SubdiagnosticKind::Suggestion { .. } => {
throw_span_err!(span, "suggestion without `code = \"...\"`")
}
@ -626,7 +633,7 @@ impl SubdiagnosticKind {
| SubdiagnosticKind::MultipartSuggestion { .. } => {}
}
Ok((kind, slug))
Ok(Some((kind, slug)))
}
}
@ -654,3 +661,7 @@ impl quote::IdentFragment for SubdiagnosticKind {
pub(super) fn should_generate_set_arg(field: &Field) -> bool {
field.attrs.is_empty()
}
pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
attr.path.segments.last().unwrap().ident.to_string() == "doc"
}

View File

@ -749,3 +749,12 @@ struct SubdiagnosticEagerSuggestion {
#[subdiagnostic(eager)]
sub: SubdiagnosticWithSuggestion,
}
/// with a doc comment on the type..
#[derive(Diagnostic)]
#[diag(compiletest::example, code = "E0123")]
struct WithDocComment {
/// ..and the field
#[primary_span]
span: Span,
}

View File

@ -641,3 +641,24 @@ struct BJ {
span: Span,
r#type: String,
}
/// with a doc comment on the type..
#[derive(Subdiagnostic)]
#[label(parser::add_paren)]
struct BK {
/// ..and the field
#[primary_span]
span: Span,
}
/// with a doc comment on the type..
#[derive(Subdiagnostic)]
enum BL {
/// ..and the variant..
#[label(parser::add_paren)]
Foo {
/// ..and the field
#[primary_span]
span: Span,
}
}