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

View File

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

View File

@ -477,7 +477,12 @@ impl SubdiagnosticKind {
pub(super) fn from_attr( pub(super) fn from_attr(
attr: &Attribute, attr: &Attribute,
fields: &impl HasFieldMap, 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 span = attr.span().unwrap();
let name = attr.path.segments.last().unwrap().ident.to_string(); let name = attr.path.segments.last().unwrap().ident.to_string();
@ -526,7 +531,9 @@ impl SubdiagnosticKind {
| SubdiagnosticKind::Note | SubdiagnosticKind::Note
| SubdiagnosticKind::Help | SubdiagnosticKind::Help
| SubdiagnosticKind::Warn | SubdiagnosticKind::Warn
| SubdiagnosticKind::MultipartSuggestion { .. } => return Ok((kind, None)), | SubdiagnosticKind::MultipartSuggestion { .. } => {
return Ok(Some((kind, None)));
}
SubdiagnosticKind::Suggestion { .. } => { SubdiagnosticKind::Suggestion { .. } => {
throw_span_err!(span, "suggestion without `code = \"...\"`") throw_span_err!(span, "suggestion without `code = \"...\"`")
} }
@ -626,7 +633,7 @@ impl SubdiagnosticKind {
| SubdiagnosticKind::MultipartSuggestion { .. } => {} | 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 { pub(super) fn should_generate_set_arg(field: &Field) -> bool {
field.attrs.is_empty() 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)] #[subdiagnostic(eager)]
sub: SubdiagnosticWithSuggestion, 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, span: Span,
r#type: String, 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,
}
}