fix(derive): #[diagnosic(severity)] works for named and unnamed variants/structs now

Fixes: https://github.com/zkat/miette/issues/26
This commit is contained in:
Kat Marchán 2021-08-21 17:18:12 -07:00
parent 729ccd32da
commit adf0bc933f
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
5 changed files with 72 additions and 42 deletions

View File

@ -10,6 +10,7 @@ mod fmt;
mod help;
mod severity;
mod snippets;
mod utils;
#[proc_macro_derive(Diagnostic, attributes(diagnostic, snippet, highlight))]
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {

View File

@ -1,4 +1,4 @@
use proc_macro2::TokenStream;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
parenthesized,
@ -8,7 +8,7 @@ use syn::{
use crate::diagnostic::DiagnosticVariant;
pub struct Severity(pub syn::Path);
pub struct Severity(pub syn::Ident);
impl Parse for Severity {
fn parse(input: ParseStream) -> syn::Result<Self> {
@ -21,20 +21,40 @@ impl Parse for Severity {
let la = content.lookahead1();
if la.peek(syn::LitStr) {
let str = content.parse::<syn::LitStr>()?;
Ok(Severity(str.parse()?))
let sev = get_severity(&str.value(), str.span())?;
Ok(Severity(syn::Ident::new(&sev, str.span())))
} else {
let path = content.parse::<syn::Path>()?;
Ok(Severity(path))
let ident = content.parse::<syn::Ident>()?;
let sev = get_severity(&ident.to_string(), ident.span())?;
Ok(Severity(syn::Ident::new(&sev, ident.span())))
}
} else {
input.parse::<Token![=]>()?;
Ok(Severity(input.parse::<syn::LitStr>()?.parse()?))
let str = input.parse::<syn::LitStr>()?;
let sev = get_severity(&str.value(), str.span())?;
Ok(Severity(syn::Ident::new(&sev, str.span())))
}
} else {
Err(syn::Error::new(ident.span(), "not a severity level."))
Err(syn::Error::new(
ident.span(),
"MIETTE BUG: not a severity option",
))
}
}
}
fn get_severity(input: &str, span: Span) -> syn::Result<String> {
match input.to_lowercase().as_ref() {
"error" | "err" => Ok("Error".into()),
"warning" | "warn" => Ok("Warning".into()),
"advice" | "adv" | "info" => Ok("Advice".into()),
_ => Err(syn::Error::new(
span,
"Invalid severity level. Only Error, Warning, and Advice are supported.",
)),
}
}
impl Severity {
pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option<TokenStream> {
let sev_pairs = variants
@ -42,10 +62,15 @@ impl Severity {
.filter(|v| v.severity.is_some())
.map(
|DiagnosticVariant {
ident, severity, ..
ident, severity, fields, ..
}| {
let severity = &severity.as_ref().unwrap().0;
quote! { Self::#ident => std::option::Option::Some(miette::Severity::#severity), }
let fields = match fields {
syn::Fields::Named(_) => quote! { { .. } },
syn::Fields::Unnamed(_) => quote! { (..) },
syn::Fields::Unit => quote!{},
};
quote! { Self::#ident #fields => std::option::Option::Some(miette::Severity::#severity), }
},
)
.collect::<Vec<_>>();

View File

@ -1,7 +1,7 @@
use std::collections::HashMap;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
@ -10,6 +10,7 @@ use syn::{
};
use crate::diagnostic::DiagnosticVariant;
use crate::utils::MemberOrString;
pub struct Snippets(Vec<Snippet>);
@ -33,37 +34,6 @@ struct HighlightAttr {
snippet: syn::Member,
}
enum MemberOrString {
Member(syn::Member),
String(syn::LitStr),
}
impl ToTokens for MemberOrString {
fn to_tokens(&self, tokens: &mut TokenStream) {
use MemberOrString::*;
match self {
Member(member) => member.to_tokens(tokens),
String(string) => string.to_tokens(tokens),
}
}
}
impl Parse for MemberOrString {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::Ident) || lookahead.peek(syn::LitInt) {
Ok(MemberOrString::Member(input.parse()?))
} else if lookahead.peek(syn::LitStr) {
Ok(MemberOrString::String(input.parse()?))
} else {
Err(syn::Error::new(
input.span(),
"Expected a string or a field reference.",
))
}
}
}
impl Parse for SnippetAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let punc = Punctuated::<MemberOrString, Token![,]>::parse_terminated(input)?;

View File

@ -0,0 +1,34 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
pub(crate) enum MemberOrString {
Member(syn::Member),
String(syn::LitStr),
}
impl ToTokens for MemberOrString {
fn to_tokens(&self, tokens: &mut TokenStream) {
use MemberOrString::*;
match self {
Member(member) => member.to_tokens(tokens),
String(string) => string.to_tokens(tokens),
}
}
}
impl Parse for MemberOrString {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::Ident) || lookahead.peek(syn::LitInt) {
Ok(MemberOrString::Member(input.parse()?))
} else if lookahead.peek(syn::LitStr) {
Ok(MemberOrString::String(input.parse()?))
} else {
Err(syn::Error::new(
input.span(),
"Expected a string or a field reference.",
))
}
}
}

View File

@ -92,7 +92,7 @@ fn path_code() {
fn path_severity() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz), severity(Warning))]
#[diagnostic(code(foo::bar::baz), severity("warning"))]
struct FooStruct;
assert_eq!(Some(Severity::Warning), FooStruct.severity());