Skip to content

Commit

Permalink
feat(labels): replace snippet stuff with simpler labels (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Sep 20, 2021
1 parent 8a0f71e commit 0ef2853
Show file tree
Hide file tree
Showing 17 changed files with 729 additions and 754 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
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
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
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
@@ -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
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

0 comments on commit 0ef2853

Please sign in to comment.