From 7b7061dd898b5a9c06477941e2d0840e27b9c67b Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 7 May 2022 06:02:11 +0100 Subject: [PATCH] macros: spanless subdiagnostics from `()` fields Type attributes could previously be used to support spanless subdiagnostics but these couldn't easily be made optional in the same way that spanned subdiagnostics could by using a field attribute on a field with an `Option` type. Spanless subdiagnostics can now be specified on fields with `()` type or `Option<()>` type. Signed-off-by: David Wood --- .../src/diagnostics/diagnostic.rs | 53 +++++++++++++----- .../rustc_macros/src/diagnostics/utils.rs | 54 +++++++++++-------- compiler/rustc_macros/src/lib.rs | 1 + .../session-diagnostic/diagnostic-derive.rs | 22 ++++++++ 4 files changed, 97 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index 83fc7bcde8a..6788ce37eae 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -5,10 +5,10 @@ use crate::diagnostics::error::{ SessionDiagnosticDeriveError, }; use crate::diagnostics::utils::{ - report_error_if_not_applied_to_span, type_matches_path, Applicability, FieldInfo, FieldInnerTy, - HasFieldMap, SetOnce, + report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path, + Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce, }; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use std::collections::HashMap; use std::str::FromStr; @@ -388,7 +388,8 @@ impl SessionDiagnosticDeriveBuilder { ) -> Result { let diag = &self.diag; - let name = attr.path.segments.last().unwrap().ident.to_string(); + let ident = &attr.path.segments.last().unwrap().ident; + let name = ident.to_string(); let name = name.as_str(); let meta = attr.parse_meta()?; @@ -405,9 +406,18 @@ impl SessionDiagnosticDeriveBuilder { #diag.set_span(#binding); }) } - "label" | "note" | "help" => { + "label" => { report_error_if_not_applied_to_span(attr, &info)?; - Ok(self.add_subdiagnostic(binding, name, name)) + Ok(self.add_spanned_subdiagnostic(binding, ident, name)) + } + "note" | "help" => { + if type_matches_path(&info.ty, &["rustc_span", "Span"]) { + Ok(self.add_spanned_subdiagnostic(binding, ident, name)) + } else if type_is_unit(&info.ty) { + Ok(self.add_subdiagnostic(ident, name)) + } else { + report_type_error(attr, "`Span` or `()`")?; + } } "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }), _ => throw_invalid_attr!(attr, &meta, |diag| { @@ -416,9 +426,18 @@ impl SessionDiagnosticDeriveBuilder { }), }, Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name { - "label" | "note" | "help" => { + "label" => { report_error_if_not_applied_to_span(attr, &info)?; - Ok(self.add_subdiagnostic(binding, name, &s.value())) + Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value())) + } + "note" | "help" => { + if type_matches_path(&info.ty, &["rustc_span", "Span"]) { + Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value())) + } else if type_is_unit(&info.ty) { + Ok(self.add_subdiagnostic(ident, &s.value())) + } else { + report_type_error(attr, "`Span` or `()`")?; + } } _ => throw_invalid_attr!(attr, &meta, |diag| { diag.help("only `label`, `note` and `help` are valid field attributes") @@ -510,12 +529,12 @@ impl SessionDiagnosticDeriveBuilder { } } - /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and - /// `fluent_attr_identifier`. - fn add_subdiagnostic( + /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug + /// and `fluent_attr_identifier`. + fn add_spanned_subdiagnostic( &self, field_binding: TokenStream, - kind: &str, + kind: &Ident, fluent_attr_identifier: &str, ) -> TokenStream { let diag = &self.diag; @@ -531,6 +550,16 @@ impl SessionDiagnosticDeriveBuilder { } } + /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug + /// and `fluent_attr_identifier`. + fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: &str) -> TokenStream { + let diag = &self.diag; + let slug = self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or("missing-slug"); + quote! { + #diag.#kind(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier)); + } + } + fn span_and_applicability_of_ty( &self, info: FieldInfo<'_>, diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index aba861fc6aa..af5a30880e0 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -4,7 +4,7 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use std::collections::BTreeSet; use std::str::FromStr; -use syn::{spanned::Spanned, Attribute, Meta, Type, Visibility}; +use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple, Visibility}; use synstructure::BindingInfo; /// Checks whether the type name of `ty` matches `name`. @@ -25,7 +25,35 @@ pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool { } } -/// Reports an error if the field's type is not `Applicability`. +/// Checks whether the type `ty` is `()`. +pub(crate) fn type_is_unit(ty: &Type) -> bool { + if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false } +} + +/// Reports a type error for field with `attr`. +pub(crate) fn report_type_error( + attr: &Attribute, + ty_name: &str, +) -> Result { + let name = attr.path.segments.last().unwrap().ident.to_string(); + let meta = attr.parse_meta()?; + + throw_span_err!( + attr.span().unwrap(), + &format!( + "the `#[{}{}]` attribute can only be applied to fields of type {}", + name, + match meta { + Meta::Path(_) => "", + Meta::NameValue(_) => " = ...", + Meta::List(_) => "(...)", + }, + ty_name + ) + ); +} + +/// Reports an error if the field's type does not match `path`. fn report_error_if_not_applied_to_ty( attr: &Attribute, info: &FieldInfo<'_>, @@ -33,23 +61,7 @@ fn report_error_if_not_applied_to_ty( ty_name: &str, ) -> Result<(), SessionDiagnosticDeriveError> { if !type_matches_path(&info.ty, path) { - let name = attr.path.segments.last().unwrap().ident.to_string(); - let name = name.as_str(); - let meta = attr.parse_meta()?; - - throw_span_err!( - attr.span().unwrap(), - &format!( - "the `#[{}{}]` attribute can only be applied to fields of type `{}`", - name, - match meta { - Meta::Path(_) => "", - Meta::NameValue(_) => " = ...", - Meta::List(_) => "(...)", - }, - ty_name - ) - ); + report_type_error(attr, ty_name)?; } Ok(()) @@ -64,7 +76,7 @@ pub(crate) fn report_error_if_not_applied_to_applicability( attr, info, &["rustc_errors", "Applicability"], - "Applicability", + "`Applicability`", ) } @@ -73,7 +85,7 @@ pub(crate) fn report_error_if_not_applied_to_span( attr: &Attribute, info: &FieldInfo<'_>, ) -> Result<(), SessionDiagnosticDeriveError> { - report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "Span") + report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "`Span`") } /// Inner type of a field and type of wrapper. diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index b01e01414e8..0baebdb7130 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -1,5 +1,6 @@ #![feature(allow_internal_unstable)] #![feature(let_else)] +#![feature(never_type)] #![feature(proc_macro_diagnostic)] #![allow(rustc::default_hash_types)] #![recursion_limit = "128"] diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs index c63410fa35b..1bc7475eca7 100644 --- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs +++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs @@ -482,3 +482,25 @@ struct VecField { #[label] spans: Vec, } + +#[derive(SessionDiagnostic)] +#[error(code = "E0123", slug = "foo")] +struct UnitField { + #[primary_span] + spans: Span, + #[help] + foo: (), + #[help = "a"] + bar: (), +} + +#[derive(SessionDiagnostic)] +#[error(code = "E0123", slug = "foo")] +struct OptUnitField { + #[primary_span] + spans: Span, + #[help] + foo: Option<()>, + #[help = "a"] + bar: Option<()>, +}