Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add labels/source code #62

Merged
merged 14 commits into from
Sep 20, 2021
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ unicode-width = { version = "0.1.8", optional = true }
supports-hyperlinks = { version = "1.1.0", optional = true }
supports-color = { version = "1.0.2", optional = true }
supports-unicode = { version = "1.0.0", optional = true }
itertools = { version = "0.10.1", optional = true }

[dev-dependencies]
semver = "1.0.4"
Expand All @@ -47,7 +48,8 @@ fancy = [
"unicode-width",
"supports-hyperlinks",
"supports-color",
"supports-unicode"
"supports-unicode",
"itertools"
]

[workspace]
Expand Down
47 changes: 30 additions & 17 deletions miette-derive/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use crate::code::Code;
use crate::diagnostic_arg::DiagnosticArg;
use crate::forward::{Forward, WhichFn};
use crate::help::Help;
use crate::label::Labels;
use crate::severity::Severity;
use crate::snippets::Snippets;
use crate::source_code::SourceCode;
use crate::url::Url;

pub enum Diagnostic {
Expand All @@ -32,7 +33,7 @@ pub struct DiagnosticDef {

pub enum DiagnosticDefArgs {
Transparent(Forward),
Concrete(DiagnosticConcreteArgs),
Concrete(Box<DiagnosticConcreteArgs>),
}

impl DiagnosticDefArgs {
Expand All @@ -59,7 +60,8 @@ pub struct DiagnosticConcreteArgs {
pub code: Option<Code>,
pub severity: Option<Severity>,
pub help: Option<Help>,
pub snippets: Option<Snippets>,
pub labels: Option<Labels>,
pub source_code: Option<SourceCode>,
pub url: Option<Url>,
pub forward: Option<Forward>,
}
Expand Down Expand Up @@ -99,14 +101,16 @@ impl DiagnosticConcreteArgs {
}
}
}
let snippets = Snippets::from_fields(fields)?;
let labels = Labels::from_fields(fields)?;
let source_code = SourceCode::from_fields(fields)?;
let concrete = DiagnosticConcreteArgs {
code,
help,
severity,
snippets,
labels,
url,
forward,
source_code,
};
Ok(concrete)
}
Expand Down Expand Up @@ -141,7 +145,7 @@ impl DiagnosticDefArgs {
.into_iter()
.filter(|x| !matches!(x, DiagnosticArg::Transparent));
let concrete = DiagnosticConcreteArgs::parse(ident, fields, attr, args)?;
Ok(DiagnosticDefArgs::Concrete(concrete))
Ok(DiagnosticDefArgs::Concrete(Box::new(concrete)))
}
}

Expand Down Expand Up @@ -208,16 +212,18 @@ impl Diagnostic {
let code_method = forward.gen_struct_method(WhichFn::Code);
let help_method = forward.gen_struct_method(WhichFn::Help);
let url_method = forward.gen_struct_method(WhichFn::Url);
let labels_method = forward.gen_struct_method(WhichFn::Labels);
let source_code_method = forward.gen_struct_method(WhichFn::SourceCode);
let severity_method = forward.gen_struct_method(WhichFn::Severity);
let snippets_method = forward.gen_struct_method(WhichFn::Snippets);

quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
#code_method
#help_method
#url_method
#labels_method
#severity_method
#snippets_method
#source_code_method
}
}
}
Expand All @@ -243,24 +249,29 @@ impl Diagnostic {
.as_ref()
.and_then(|x| x.gen_struct())
.or_else(|| forward(WhichFn::Severity));
let snip_body = concrete
.snippets
.as_ref()
.and_then(|x| x.gen_struct(fields))
.or_else(|| forward(WhichFn::Snippets));
let url_body = concrete
.url
.as_ref()
.and_then(|x| x.gen_struct(ident, fields))
.or_else(|| forward(WhichFn::Url));

let labels_body = concrete
.labels
.as_ref()
.and_then(|x| x.gen_struct(fields))
.or_else(|| forward(WhichFn::Labels));
let src_body = concrete
.source_code
.as_ref()
.and_then(|x| x.gen_struct(fields))
.or_else(|| forward(WhichFn::SourceCode));
quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
#code_body
#help_body
#sev_body
#snip_body
#url_body
#labels_body
#src_body
}
}
}
Expand All @@ -275,14 +286,16 @@ impl Diagnostic {
let code_body = Code::gen_enum(variants);
let help_body = Help::gen_enum(variants);
let sev_body = Severity::gen_enum(variants);
let snip_body = Snippets::gen_enum(variants);
let labels_body = Labels::gen_enum(variants);
let src_body = SourceCode::gen_enum(variants);
let url_body = Url::gen_enum(ident, variants);
quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
#code_body
#help_body
#sev_body
#snip_body
#labels_body
#src_body
#url_body
}
}
Expand Down
14 changes: 8 additions & 6 deletions miette-derive/src/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ pub enum WhichFn {
Help,
Url,
Severity,
Snippets,
Labels,
SourceCode,
}

impl WhichFn {
Expand All @@ -45,7 +46,8 @@ impl WhichFn {
Self::Help => quote! { help() },
Self::Url => quote! { url() },
Self::Severity => quote! { severity() },
Self::Snippets => quote! { snippets() },
Self::Labels => quote! { labels() },
Self::SourceCode => quote! { source_code() },
}
}

Expand All @@ -63,11 +65,11 @@ impl WhichFn {
Self::Severity => quote! {
fn severity(&self) -> std::option::Option<miette::Severity>
},
Self::Snippets => quote! {
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>>
Self::Labels => quote! {
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>>
},
Self::Related => quote! {
fn related<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &'a dyn miette::Diagnostic> + 'a>>
Self::SourceCode => quote! {
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode>
},
}
}
Expand Down
179 changes: 179 additions & 0 deletions miette-derive/src/label.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
spanned::Spanned,
Token,
};

use crate::{
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
fmt::{self, Display},
forward::WhichFn,
utils::{display_pat_members, gen_all_variants_with},
};

pub struct Labels(Vec<Label>);

struct Label {
label: Option<Display>,
span: syn::Member,
}

struct LabelAttr {
label: Option<Display>,
}

impl Parse for LabelAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let la = input.lookahead1();
let label = if la.peek(syn::token::Paren) {
// #[label("{}", x)]
let content;
parenthesized!(content in input);
if content.peek(syn::LitStr) {
let fmt = content.parse()?;
let args = if content.is_empty() {
TokenStream::new()
} else {
fmt::parse_token_expr(&content, false)?
};
let display = Display {
fmt,
args,
has_bonus_display: false,
};
Some(display)
} else {
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The first argument must be a literal string."));
}
} else if la.peek(Token![=]) {
// #[label = "blabla"]
input.parse::<Token![=]>()?;
Some(Display {
fmt: input.parse()?,
args: TokenStream::new(),
has_bonus_display: false,
})
} else {
None
};
Ok(LabelAttr { label })
}
}

impl Labels {
pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
match fields {
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
syn::Fields::Unnamed(unnamed) => {
Self::from_fields_vec(unnamed.unnamed.iter().collect())
}
syn::Fields::Unit => Ok(None),
}
}

fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
let mut labels = Vec::new();
for (i, field) in fields.iter().enumerate() {
for attr in &field.attrs {
if attr.path.is_ident("label") {
let span = if let Some(ident) = field.ident.clone() {
syn::Member::Named(ident)
} else {
syn::Member::Unnamed(syn::Index {
index: i as u32,
span: field.span(),
})
};
let LabelAttr { label } = syn::parse2::<LabelAttr>(attr.tokens.clone())?;
labels.push(Label { label, span });
}
}
}
if labels.is_empty() {
Ok(None)
} else {
Ok(Some(Labels(labels)))
}
}

pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
let (display_pat, display_members) = display_pat_members(fields);
let labels = self.0.iter().map(|highlight| {
let Label { span, label } = highlight;
if let Some(display) = label {
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
quote! {
miette::LabeledSpan::new_with_span(
std::option::Option::Some(format!(#fmt #args)),
self.#span.clone(),
)
}
} else {
quote! {
miette::LabeledSpan::new_with_span(
std::option::Option::None,
self.#span.clone(),
)
}
}
});
Some(quote! {
#[allow(unused_variables)]
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
let Self #display_pat = self;
Some(Box::new(vec![
#(#labels),*
].into_iter()))
}
})
}

pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
gen_all_variants_with(
variants,
WhichFn::Labels,
|ident, fields, DiagnosticConcreteArgs { labels, .. }| {
let (display_pat, display_members) = display_pat_members(fields);
labels.as_ref().and_then(|labels| {
let variant_labels = labels.0.iter().map(|label| {
let Label { span, label } = label;
let field = match &span {
syn::Member::Named(ident) => ident.clone(),
syn::Member::Unnamed(syn::Index { index, .. }) => {
format_ident!("_{}", index)
}
};
if let Some(display) = label {
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
quote! {
miette::LabeledSpan::new_with_span(
std::option::Option::Some(format!(#fmt #args)),
#field.clone(),
)
}
} else {
quote! {
miette::LabeledSpan::new_with_span(
std::option::Option::None,
#field.clone(),
)
}
}
});
let variant_name = ident.clone();
match &fields {
syn::Fields::Unit => None,
_ => Some(quote! {
Self::#variant_name #display_pat => std::option::Option::Some(std::boxed::Box::new(vec![
#(#variant_labels),*
].into_iter())),
}),
}
})
},
)
}
}
5 changes: 3 additions & 2 deletions miette-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ mod diagnostic_arg;
mod fmt;
mod forward;
mod help;
mod label;
mod severity;
mod snippets;
mod source_code;
mod url;
mod utils;

#[proc_macro_derive(Diagnostic, attributes(diagnostic, snippet, highlight))]
#[proc_macro_derive(Diagnostic, attributes(diagnostic, label, source_code))]
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let cmd = match Diagnostic::from_derive_input(input) {
Expand Down
Loading