mirror of https://github.com/zkat/miette.git
fix(derive): move to plain syn to fix darling issues
This commit is contained in:
parent
027c3b0a94
commit
9a78a94395
|
|
@ -14,4 +14,3 @@ proc-macro = true
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = "1.0.45"
|
syn = "1.0.45"
|
||||||
darling = "0.13.0"
|
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,44 @@
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use darling::{ast::Fields, error::Error as DarlingError, FromMeta};
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{Lit, Meta, NestedMeta};
|
use syn::{
|
||||||
|
parenthesized,
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
Token,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{Diagnostic, DiagnosticField, DiagnosticVariant};
|
use crate::diagnostic::{Diagnostic, DiagnosticVariant};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Code(String);
|
pub struct Code(pub String);
|
||||||
|
|
||||||
impl Display for Code {
|
impl Parse for Code {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
write!(f, "{}", self.0)
|
let ident = input.parse::<syn::Ident>()?;
|
||||||
}
|
if ident == "code" {
|
||||||
}
|
let la = input.lookahead1();
|
||||||
|
if la.peek(syn::token::Paren) {
|
||||||
impl FromMeta for Code {
|
let content;
|
||||||
fn from_string(arg: &str) -> Result<Self, DarlingError> {
|
parenthesized!(content in input);
|
||||||
Ok(Code(arg.into()))
|
let la = content.lookahead1();
|
||||||
}
|
if la.peek(syn::LitStr) {
|
||||||
|
let str = content.parse::<syn::LitStr>()?;
|
||||||
fn from_list(items: &[NestedMeta]) -> Result<Self, DarlingError> {
|
Ok(Code(str.value()))
|
||||||
match &items[0] {
|
} else {
|
||||||
NestedMeta::Meta(Meta::Path(p)) => Ok(Code(
|
let path = content.parse::<syn::Path>()?;
|
||||||
p.segments
|
Ok(Code(
|
||||||
.iter()
|
path.segments
|
||||||
.map(|s| s.ident.to_string())
|
.iter()
|
||||||
.collect::<Vec<_>>()
|
.map(|s| s.ident.to_string())
|
||||||
.join("::"),
|
.collect::<Vec<_>>()
|
||||||
)),
|
.join("::"),
|
||||||
NestedMeta::Lit(Lit::Str(code)) => Ok(Code(code.value())),
|
))
|
||||||
_ => Err(DarlingError::custom(
|
}
|
||||||
"invalid code format. Only path::style and string literals are accepted",
|
} else {
|
||||||
)),
|
input.parse::<Token![=]>()?;
|
||||||
|
Ok(Code(input.parse::<syn::LitStr>()?.value()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(syn::Error::new(ident.span(), "diagnostic code is required. Use #[diagnostic(code = ...)] or #[diagnostic(code(...))] to define one."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,16 +46,25 @@ impl FromMeta for Code {
|
||||||
impl Code {
|
impl Code {
|
||||||
pub(crate) fn gen_enum(
|
pub(crate) fn gen_enum(
|
||||||
_diag: &Diagnostic,
|
_diag: &Diagnostic,
|
||||||
variants: &[&DiagnosticVariant],
|
variants: &[DiagnosticVariant],
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let code_pairs = variants.iter().map(
|
let code_pairs = variants.iter().map(
|
||||||
|DiagnosticVariant {
|
|DiagnosticVariant {
|
||||||
ref ident,
|
ref ident,
|
||||||
ref code,
|
ref code,
|
||||||
|
ref fields,
|
||||||
..
|
..
|
||||||
}| {
|
}| {
|
||||||
let code = code.to_string();
|
let code = &code.0;
|
||||||
quote! { Self::#ident => std::boxed::Box::new(#code), }
|
match fields {
|
||||||
|
syn::Fields::Named(_) => {
|
||||||
|
quote! { Self::#ident { .. } => std::boxed::Box::new(#code), }
|
||||||
|
}
|
||||||
|
syn::Fields::Unnamed(_) => {
|
||||||
|
quote! { Self::#ident(..) => std::boxed::Box::new(#code), }
|
||||||
|
}
|
||||||
|
syn::Fields::Unit => quote! { Self::#ident => std::boxed::Box::new(#code), },
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
|
|
@ -62,15 +76,8 @@ impl Code {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_struct(
|
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||||
diag: &Diagnostic,
|
let code = &self.0;
|
||||||
_fields: &Fields<&DiagnosticField>,
|
|
||||||
) -> Option<TokenStream> {
|
|
||||||
let code = diag
|
|
||||||
.code
|
|
||||||
.as_ref()
|
|
||||||
.expect("`code` attribute is required for diagnostics.")
|
|
||||||
.to_string();
|
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
|
fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
|
||||||
std::boxed::Box::new(#code)
|
std::boxed::Box::new(#code)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{punctuated::Punctuated, DeriveInput, Token};
|
||||||
|
|
||||||
|
use crate::code::Code;
|
||||||
|
use crate::diagnostic_arg::DiagnosticArg;
|
||||||
|
use crate::help::Help;
|
||||||
|
use crate::severity::Severity;
|
||||||
|
|
||||||
|
pub enum Diagnostic {
|
||||||
|
Struct {
|
||||||
|
ident: syn::Ident,
|
||||||
|
generics: syn::Generics,
|
||||||
|
code: Code,
|
||||||
|
severity: Option<Severity>,
|
||||||
|
help: Option<Help>,
|
||||||
|
},
|
||||||
|
Enum {
|
||||||
|
ident: syn::Ident,
|
||||||
|
generics: syn::Generics,
|
||||||
|
variants: Vec<DiagnosticVariant>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DiagnosticVariant {
|
||||||
|
pub ident: syn::Ident,
|
||||||
|
pub fields: syn::Fields,
|
||||||
|
pub code: Code,
|
||||||
|
pub severity: Option<Severity>,
|
||||||
|
pub help: Option<Help>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic {
|
||||||
|
pub fn from_derive_input(input: DeriveInput) -> Result<Self, syn::Error> {
|
||||||
|
Ok(match input.data {
|
||||||
|
syn::Data::Struct(_) => {
|
||||||
|
if let Some(attr) = input.attrs.iter().find(|x| x.path.is_ident("diagnostic")) {
|
||||||
|
let args = attr.parse_args_with(
|
||||||
|
Punctuated::<DiagnosticArg, Token![,]>::parse_terminated,
|
||||||
|
)?;
|
||||||
|
let mut code = None;
|
||||||
|
let mut severity = None;
|
||||||
|
let mut help = None;
|
||||||
|
for arg in args {
|
||||||
|
match arg {
|
||||||
|
DiagnosticArg::Code(new_code) => {
|
||||||
|
// TODO: error on multiple?
|
||||||
|
code = Some(new_code);
|
||||||
|
}
|
||||||
|
DiagnosticArg::Severity(sev) => {
|
||||||
|
severity = Some(sev);
|
||||||
|
}
|
||||||
|
DiagnosticArg::Help(hl) => {
|
||||||
|
help = Some(hl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ident = input.ident.clone();
|
||||||
|
Diagnostic::Struct {
|
||||||
|
ident: input.ident,
|
||||||
|
generics: input.generics,
|
||||||
|
code: code.ok_or_else(|| {
|
||||||
|
syn::Error::new(ident.span(), "Diagnostic code is required.")
|
||||||
|
})?,
|
||||||
|
help,
|
||||||
|
severity,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Also handle when there's multiple `#[diagnostic]` attrs?
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
input.ident.span(),
|
||||||
|
"#[diagnostic] attribute is required when deriving Diagnostic.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
|
||||||
|
let mut vars = Vec::new();
|
||||||
|
for var in variants {
|
||||||
|
if let Some(attr) = var.attrs.iter().find(|x| x.path.is_ident("diagnostic")) {
|
||||||
|
let args = attr.parse_args_with(
|
||||||
|
Punctuated::<DiagnosticArg, Token![,]>::parse_terminated,
|
||||||
|
)?;
|
||||||
|
let mut code = None;
|
||||||
|
let mut severity = None;
|
||||||
|
let mut help = None;
|
||||||
|
for arg in args {
|
||||||
|
match arg {
|
||||||
|
DiagnosticArg::Code(new_code) => {
|
||||||
|
// TODO: error on multiple?
|
||||||
|
code = Some(new_code);
|
||||||
|
}
|
||||||
|
DiagnosticArg::Severity(sev) => {
|
||||||
|
severity = Some(sev);
|
||||||
|
}
|
||||||
|
DiagnosticArg::Help(hl) => {
|
||||||
|
help = Some(hl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ident = input.ident.clone();
|
||||||
|
vars.push(DiagnosticVariant {
|
||||||
|
ident: var.ident,
|
||||||
|
fields: var.fields,
|
||||||
|
code: code.ok_or_else(|| {
|
||||||
|
syn::Error::new(ident.span(), "Diagnostic code is required.")
|
||||||
|
})?,
|
||||||
|
help,
|
||||||
|
severity,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Also handle when there's multiple `#[diagnostic]` attrs?
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
var.ident.span(),
|
||||||
|
"#[diagnostic] attribute is required on all enum variants when deriving Diagnostic.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Diagnostic::Enum {
|
||||||
|
ident: input.ident,
|
||||||
|
generics: input.generics,
|
||||||
|
variants: vars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syn::Data::Union(_) => {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
input.ident.span(),
|
||||||
|
"Can't derive Diagnostic for Unions",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen(&self) -> TokenStream {
|
||||||
|
match self {
|
||||||
|
Self::Struct {
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
code,
|
||||||
|
severity,
|
||||||
|
help,
|
||||||
|
} => {
|
||||||
|
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
||||||
|
let code_body = code.gen_struct();
|
||||||
|
let help_body = help.as_ref().and_then(|x| x.gen_struct());
|
||||||
|
let sev_body = severity.as_ref().and_then(|x| x.gen_struct());
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||||
|
#code_body
|
||||||
|
#help_body
|
||||||
|
#sev_body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Enum {
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
variants,
|
||||||
|
} => {
|
||||||
|
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
||||||
|
let code_body = Code::gen_enum(self, variants);
|
||||||
|
let help_body = Help::gen_enum(self, variants);
|
||||||
|
let sev_body = Severity::gen_enum(self, variants);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||||
|
#code_body
|
||||||
|
#help_body
|
||||||
|
#sev_body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
|
||||||
|
use crate::code::Code;
|
||||||
|
use crate::help::Help;
|
||||||
|
use crate::severity::Severity;
|
||||||
|
|
||||||
|
pub enum DiagnosticArg {
|
||||||
|
Code(Code),
|
||||||
|
Severity(Severity),
|
||||||
|
Help(Help),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for DiagnosticArg {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let ident = input.fork().parse::<syn::Ident>()?;
|
||||||
|
if ident == "code" {
|
||||||
|
Ok(DiagnosticArg::Code(input.parse()?))
|
||||||
|
} else if ident == "severity" {
|
||||||
|
Ok(DiagnosticArg::Severity(input.parse()?))
|
||||||
|
} else if ident == "help" {
|
||||||
|
Ok(DiagnosticArg::Help(input.parse()?))
|
||||||
|
} else {
|
||||||
|
Err(syn::Error::new(ident.span(), "Unrecognized diagnostic option"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,50 +1,47 @@
|
||||||
use darling::{ast::Fields, error::Error as DarlingError, FromMeta};
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{Lit, NestedMeta};
|
use syn::{
|
||||||
|
parenthesized,
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
Token,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{Diagnostic, DiagnosticField, DiagnosticVariant};
|
use crate::diagnostic::{Diagnostic, DiagnosticVariant};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Help {
|
pub struct Help {
|
||||||
pub fmt: String,
|
pub fmt: String,
|
||||||
pub args: Vec<NestedMeta>,
|
pub args: Vec<syn::Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromMeta for Help {
|
impl Parse for Help {
|
||||||
fn from_string(arg: &str) -> Result<Help, DarlingError> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
Ok(Help {
|
let ident = input.parse::<syn::Ident>()?;
|
||||||
fmt: arg.into(),
|
if ident == "help" {
|
||||||
args: Vec::new(),
|
let la = input.lookahead1();
|
||||||
})
|
if la.peek(syn::token::Paren) {
|
||||||
}
|
let content;
|
||||||
|
parenthesized!(content in input);
|
||||||
fn from_list(items: &[NestedMeta]) -> Result<Help, DarlingError> {
|
let str = content.parse::<syn::LitStr>()?;
|
||||||
match &items.get(0) {
|
Ok(Help {
|
||||||
Some(NestedMeta::Lit(Lit::Str(fmt))) => Ok(Help {
|
fmt: str.value(),
|
||||||
fmt: fmt.value(),
|
args: Vec::new(),
|
||||||
args: items[1..]
|
})
|
||||||
.iter()
|
} else {
|
||||||
.map(|item| match item {
|
input.parse::<Token![=]>()?;
|
||||||
NestedMeta::Meta(_) => Err(DarlingError::custom(
|
Ok(Help {
|
||||||
"Only literals are supported for now. Sorry :("
|
fmt: input.parse::<syn::LitStr>()?.value(),
|
||||||
)),
|
args: Vec::new(),
|
||||||
NestedMeta::Lit(_) => Ok(item.clone()),
|
})
|
||||||
})
|
}
|
||||||
.collect::<Result<Vec<_>, DarlingError>>()?,
|
} else {
|
||||||
}),
|
Err(syn::Error::new(ident.span(), "not a help"))
|
||||||
None => Err(DarlingError::custom("Help format string is required")),
|
|
||||||
_ => Err(DarlingError::custom(
|
|
||||||
"First argument must be a literal format string",
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Help {
|
impl Help {
|
||||||
pub(crate) fn gen_enum(
|
pub(crate) fn gen_enum(
|
||||||
_diag: &Diagnostic,
|
_diag: &Diagnostic,
|
||||||
variants: &[&DiagnosticVariant],
|
variants: &[DiagnosticVariant],
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let help_pairs = variants
|
let help_pairs = variants
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -53,12 +50,22 @@ impl Help {
|
||||||
|DiagnosticVariant {
|
|DiagnosticVariant {
|
||||||
ref ident,
|
ref ident,
|
||||||
ref help,
|
ref help,
|
||||||
|
ref fields,
|
||||||
..
|
..
|
||||||
}| {
|
}| {
|
||||||
let help = &help.as_ref().unwrap();
|
let help = &help.as_ref().unwrap();
|
||||||
let fmt = &help.fmt;
|
let fmt = &help.fmt;
|
||||||
let args = help.args.iter().map(|arg| quote! { #arg, });
|
let args = help.args.iter().map(|arg| quote! { #arg, });
|
||||||
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), }
|
match fields {
|
||||||
|
syn::Fields::Named(_) => {
|
||||||
|
quote! { Self::#ident{..} => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), }
|
||||||
|
}
|
||||||
|
syn::Fields::Unnamed(_) => {
|
||||||
|
quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), }
|
||||||
|
}
|
||||||
|
syn::Fields::Unit =>
|
||||||
|
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), },
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
@ -76,17 +83,12 @@ impl Help {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_struct(
|
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||||
diag: &Diagnostic,
|
let fmt = &self.fmt;
|
||||||
_fields: &Fields<&DiagnosticField>,
|
let args = &self.args;
|
||||||
) -> Option<TokenStream> {
|
Some(quote! {
|
||||||
diag.help.as_ref().map(|h| {
|
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||||
let fmt = &h.fmt;
|
std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*)))
|
||||||
let args = &h.args;
|
|
||||||
quote! {
|
|
||||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
|
||||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,101 +1,21 @@
|
||||||
use darling::{
|
|
||||||
ast::{self, Fields},
|
|
||||||
FromDeriveInput, FromField, FromVariant, ToTokens,
|
|
||||||
};
|
|
||||||
use proc_macro2::TokenStream;
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse_macro_input, DeriveInput};
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
use code::Code;
|
use diagnostic::Diagnostic;
|
||||||
use help::Help;
|
|
||||||
use severity::Severity;
|
|
||||||
|
|
||||||
mod code;
|
mod code;
|
||||||
|
mod diagnostic;
|
||||||
|
mod diagnostic_arg;
|
||||||
mod help;
|
mod help;
|
||||||
mod severity;
|
mod severity;
|
||||||
|
|
||||||
#[proc_macro_derive(Diagnostic, attributes(diagnostic))]
|
#[proc_macro_derive(Diagnostic, attributes(diagnostic))]
|
||||||
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
let cmd = match Diagnostic::from_derive_input(&input) {
|
let cmd = match Diagnostic::from_derive_input(input) {
|
||||||
Ok(cmd) => cmd,
|
Ok(cmd) => cmd.gen(),
|
||||||
Err(err) => return err.write_errors().into(),
|
Err(err) => return err.to_compile_error().into(),
|
||||||
};
|
};
|
||||||
// panic!("{:#}", cmd.to_token_stream());
|
// panic!("{:#}", cmd.to_token_stream());
|
||||||
quote!(#cmd).into()
|
quote!(#cmd).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, FromDeriveInput)]
|
|
||||||
#[darling(supports(any), attributes(diagnostic))]
|
|
||||||
struct Diagnostic {
|
|
||||||
ident: syn::Ident,
|
|
||||||
data: ast::Data<DiagnosticVariant, DiagnosticField>,
|
|
||||||
generics: syn::Generics,
|
|
||||||
#[darling(default)]
|
|
||||||
code: Option<Code>,
|
|
||||||
#[darling(default)]
|
|
||||||
severity: Option<Severity>,
|
|
||||||
#[darling(default)]
|
|
||||||
help: Option<Help>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, FromField)]
|
|
||||||
struct DiagnosticField {
|
|
||||||
ident: Option<syn::Ident>,
|
|
||||||
ty: syn::Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, FromVariant)]
|
|
||||||
#[darling(attributes(diagnostic))]
|
|
||||||
struct DiagnosticVariant {
|
|
||||||
ident: syn::Ident,
|
|
||||||
code: Code,
|
|
||||||
#[darling(default)]
|
|
||||||
severity: Option<Severity>,
|
|
||||||
#[darling(default)]
|
|
||||||
help: Option<Help>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for Diagnostic {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
||||||
let ts = match self.data.as_ref() {
|
|
||||||
ast::Data::Enum(variants) => self.gen_enum(variants),
|
|
||||||
ast::Data::Struct(fields) => self.gen_struct(fields),
|
|
||||||
};
|
|
||||||
tokens.extend(ts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Diagnostic {
|
|
||||||
fn gen_enum(&self, variants: Vec<&DiagnosticVariant>) -> TokenStream {
|
|
||||||
let ident = &self.ident;
|
|
||||||
let (impl_generics, ty_generics, where_clause) = &self.generics.split_for_impl();
|
|
||||||
let code_body = Code::gen_enum(self, &variants);
|
|
||||||
let help_body = Help::gen_enum(self, &variants);
|
|
||||||
let sev_body = Severity::gen_enum(self, &variants);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
|
||||||
#code_body
|
|
||||||
#help_body
|
|
||||||
#sev_body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen_struct(&self, fields: Fields<&DiagnosticField>) -> TokenStream {
|
|
||||||
let ident= &self.ident;
|
|
||||||
let (impl_generics, ty_generics, where_clause) = &self.generics.split_for_impl();
|
|
||||||
let code_body = Code::gen_struct(self, &fields);
|
|
||||||
let help_body = Help::gen_struct(self, &fields);
|
|
||||||
let sev_body = Severity::gen_struct(self, &fields);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
|
||||||
#code_body
|
|
||||||
#help_body
|
|
||||||
#sev_body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,47 @@
|
||||||
use darling::{ast::Fields, error::Error as DarlingError, FromMeta};
|
use proc_macro2::TokenStream;
|
||||||
use proc_macro2::{Span, TokenStream};
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{Lit, LitStr, Meta, NestedMeta, Path};
|
use syn::{
|
||||||
|
parenthesized,
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
Token,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{Diagnostic, DiagnosticField, DiagnosticVariant};
|
use crate::diagnostic::{Diagnostic, DiagnosticVariant};
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub struct Severity(pub syn::Path);
|
||||||
pub struct Severity(pub Path);
|
|
||||||
|
|
||||||
impl FromMeta for Severity {
|
impl Parse for Severity {
|
||||||
fn from_string(arg: &str) -> Result<Self, DarlingError> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
Ok(Severity(LitStr::new(arg, Span::call_site()).parse()?))
|
let ident = input.parse::<syn::Ident>()?;
|
||||||
}
|
if ident == "severity" {
|
||||||
|
let la = input.lookahead1();
|
||||||
fn from_list(items: &[NestedMeta]) -> Result<Self, DarlingError> {
|
if la.peek(syn::token::Paren) {
|
||||||
match &items[0] {
|
let content;
|
||||||
NestedMeta::Meta(Meta::Path(p)) => Ok(Severity(p.clone())),
|
parenthesized!(content in input);
|
||||||
NestedMeta::Lit(Lit::Str(sev)) => Ok(Severity(sev.parse()?)),
|
let la = content.lookahead1();
|
||||||
_ => Err(DarlingError::custom(
|
if la.peek(syn::LitStr) {
|
||||||
"invalid severity format. Only literal names and string literals are accepted",
|
let str = content.parse::<syn::LitStr>()?;
|
||||||
)),
|
Ok(Severity(str.parse()?))
|
||||||
|
} else {
|
||||||
|
let path = content.parse::<syn::Path>()?;
|
||||||
|
Ok(Severity(path))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.parse::<Token![=]>()?;
|
||||||
|
Ok(Severity(input.parse::<syn::LitStr>()?.parse()?))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(syn::Error::new(
|
||||||
|
ident.span(),
|
||||||
|
"not a severity level.",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Severity {
|
impl Severity {
|
||||||
pub(crate) fn gen_enum(
|
pub(crate) fn gen_enum(
|
||||||
_diag: &Diagnostic,
|
_diag: &Diagnostic,
|
||||||
variants: &[&DiagnosticVariant],
|
variants: &[DiagnosticVariant],
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let sev_pairs = variants
|
let sev_pairs = variants
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -55,13 +69,11 @@ impl Severity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_struct(diag: &Diagnostic, _fields: &Fields<&DiagnosticField>) -> Option<TokenStream> {
|
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||||
diag.severity.as_ref().map(|sev| {
|
let sev = &self.0;
|
||||||
let sev = &sev.0;
|
Some(quote! {
|
||||||
quote! {
|
fn severity(&self) -> std::option::Option<miette::Severity> {
|
||||||
fn severity(&self) -> std::option::Option<miette::Severity> {
|
Some(miette::Severity::#sev)
|
||||||
Some(miette::Severity::#sev)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ fn basic_struct() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_enum() {
|
fn basic_enum() {
|
||||||
|
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
enum Foo {
|
enum Foo {
|
||||||
|
|
@ -35,14 +34,39 @@ fn basic_enum() {
|
||||||
)]
|
)]
|
||||||
X,
|
X,
|
||||||
#[diagnostic(code = "foo::y")]
|
#[diagnostic(code = "foo::y")]
|
||||||
Y,
|
Y(usize),
|
||||||
|
#[diagnostic(code = "foo::z")]
|
||||||
|
Z { prop: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!("foo::x".to_string(), Foo::X.code().to_string());
|
assert_eq!("foo::x".to_string(), Foo::X.code().to_string());
|
||||||
assert_eq!("foo::y".to_string(), Foo::Y.code().to_string());
|
assert_eq!("foo::y".to_string(), Foo::Y(1).code().to_string());
|
||||||
|
assert_eq!(
|
||||||
|
"foo::z".to_string(),
|
||||||
|
Foo::Z { prop: "bar".into() }.code().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(Some(Severity::Warning), Foo::X.severity());
|
assert_eq!(Some(Severity::Warning), Foo::X.severity());
|
||||||
assert_eq!(None, Foo::Y.severity());
|
assert_eq!(None, Foo::Y(1).severity());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn paren_code() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("welp")]
|
||||||
|
#[diagnostic(code("foo::bar::baz"))]
|
||||||
|
struct FooStruct;
|
||||||
|
|
||||||
|
assert_eq!("foo::bar::baz".to_string(), FooStruct.code().to_string());
|
||||||
|
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("welp")]
|
||||||
|
enum FooEnum {
|
||||||
|
#[diagnostic(code("foo::x"))]
|
||||||
|
X,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!("foo::x".to_string(), FooEnum::X.code().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -68,10 +92,7 @@ fn path_code() {
|
||||||
fn path_severity() {
|
fn path_severity() {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(
|
#[diagnostic(code(foo::bar::baz), severity(Warning))]
|
||||||
code(foo::bar::baz),
|
|
||||||
severity(Warning)
|
|
||||||
)]
|
|
||||||
struct FooStruct;
|
struct FooStruct;
|
||||||
|
|
||||||
assert_eq!(Some(Severity::Warning), FooStruct.severity());
|
assert_eq!(Some(Severity::Warning), FooStruct.severity());
|
||||||
|
|
@ -79,10 +100,7 @@ fn path_severity() {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
enum FooEnum {
|
enum FooEnum {
|
||||||
#[diagnostic(
|
#[diagnostic(code(foo::x), severity(Warning))]
|
||||||
code(foo::x),
|
|
||||||
severity(Warning),
|
|
||||||
)]
|
|
||||||
X,
|
X,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,10 +111,7 @@ fn path_severity() {
|
||||||
fn list_help() {
|
fn list_help() {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(
|
#[diagnostic(code(foo::bar::baz), help("try doing it better"))]
|
||||||
code(foo::bar::baz),
|
|
||||||
help("try doing it better"),
|
|
||||||
)]
|
|
||||||
struct FooStruct;
|
struct FooStruct;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -107,10 +122,7 @@ fn list_help() {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
enum FooEnum {
|
enum FooEnum {
|
||||||
#[diagnostic(
|
#[diagnostic(code(foo::x), help("try doing it better"))]
|
||||||
code(foo::x),
|
|
||||||
help("try doing it better"),
|
|
||||||
)]
|
|
||||||
X,
|
X,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,8 +132,6 @@ fn list_help() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Darling doesn't support this, apparently:
|
|
||||||
// https://github.com/TedDriggs/darling/issues/145
|
|
||||||
/*
|
/*
|
||||||
#[test]
|
#[test]
|
||||||
fn fmt_help() {
|
fn fmt_help() {
|
||||||
|
|
@ -129,9 +139,9 @@ fn fmt_help() {
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(
|
#[diagnostic(
|
||||||
code(foo::bar::baz),
|
code(foo::bar::baz),
|
||||||
help("{} {}", 1, "bar"),
|
help("{} {}", 1, self.0),
|
||||||
)]
|
)]
|
||||||
struct FooStruct;
|
struct FooStruct(String);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"1 bar".to_string(),
|
"1 bar".to_string(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue