Skip to content

Commit

Permalink
fix(derive): move to plain syn to fix darling issues
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Aug 16, 2021
1 parent 027c3b0 commit 9a78a94
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 225 deletions.
1 change: 0 additions & 1 deletion miette-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ proc-macro = true
proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0.45"
darling = "0.13.0"
91 changes: 49 additions & 42 deletions miette-derive/src/code.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,70 @@
use std::fmt::Display;

use darling::{ast::Fields, error::Error as DarlingError, FromMeta};
use proc_macro2::TokenStream;
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)]
pub struct Code(String);

impl Display for Code {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl FromMeta for Code {
fn from_string(arg: &str) -> Result<Self, DarlingError> {
Ok(Code(arg.into()))
}
pub struct Code(pub String);

fn from_list(items: &[NestedMeta]) -> Result<Self, DarlingError> {
match &items[0] {
NestedMeta::Meta(Meta::Path(p)) => Ok(Code(
p.segments
.iter()
.map(|s| s.ident.to_string())
.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",
)),
impl Parse for Code {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident = input.parse::<syn::Ident>()?;
if ident == "code" {
let la = input.lookahead1();
if la.peek(syn::token::Paren) {
let content;
parenthesized!(content in input);
let la = content.lookahead1();
if la.peek(syn::LitStr) {
let str = content.parse::<syn::LitStr>()?;
Ok(Code(str.value()))
} else {
let path = content.parse::<syn::Path>()?;
Ok(Code(
path.segments
.iter()
.map(|s| s.ident.to_string())
.collect::<Vec<_>>()
.join("::"),
))
}
} 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."))
}
}
}

impl Code {
pub(crate) fn gen_enum(
_diag: &Diagnostic,
variants: &[&DiagnosticVariant],
variants: &[DiagnosticVariant],
) -> Option<TokenStream> {
let code_pairs = variants.iter().map(
|DiagnosticVariant {
ref ident,
ref code,
ref fields,
..
}| {
let code = code.to_string();
quote! { Self::#ident => std::boxed::Box::new(#code), }
let code = &code.0;
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! {
Expand All @@ -62,15 +76,8 @@ impl Code {
})
}

pub(crate) fn gen_struct(
diag: &Diagnostic,
_fields: &Fields<&DiagnosticField>,
) -> Option<TokenStream> {
let code = diag
.code
.as_ref()
.expect("`code` attribute is required for diagnostics.")
.to_string();
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
let code = &self.0;
Some(quote! {
fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
std::boxed::Box::new(#code)
Expand Down
175 changes: 175 additions & 0 deletions miette-derive/src/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
}
}
26 changes: 26 additions & 0 deletions miette-derive/src/diagnostic_arg.rs
Original file line number Diff line number Diff line change
@@ -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"))
}
}
}

0 comments on commit 9a78a94

Please sign in to comment.