mirror of https://github.com/zkat/miette.git
Merge 837ae4f171 into b466948965
This commit is contained in:
commit
6eadab757f
|
|
@ -10,6 +10,7 @@ license = "Apache-2.0"
|
|||
readme = "README.md"
|
||||
edition = "2018"
|
||||
rust-version = "1.82.0"
|
||||
resolver = "2"
|
||||
exclude = ["images/", "tests/", "miette-derive/"]
|
||||
|
||||
[dependencies]
|
||||
|
|
@ -31,6 +32,8 @@ syntect = { version = "5.1.0", optional = true }
|
|||
[dev-dependencies]
|
||||
thiserror = "2.0.11"
|
||||
semver = "1.0.21"
|
||||
# (kind of) hacky workaround to enable additional feature flags in tests, requires resolver = "2"
|
||||
miette = { path = ".", features = ["perfect-derive"] }
|
||||
|
||||
# Eyre devdeps
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
|
@ -47,6 +50,7 @@ strip-ansi-escapes = "0.2.0"
|
|||
[features]
|
||||
default = ["derive"]
|
||||
derive = ["dep:miette-derive"]
|
||||
perfect-derive = ["derive","miette-derive?/perfect-derive"]
|
||||
no-format-args-capture = []
|
||||
fancy-base = [
|
||||
"dep:owo-colors",
|
||||
|
|
|
|||
53
README.md
53
README.md
|
|
@ -53,6 +53,7 @@ diagnostic error code: ruget::api::bad_json
|
|||
- [... syntax highlighting](#-syntax-highlighting)
|
||||
- [... primary label](#-primary-label)
|
||||
- [... collection of labels](#-collection-of-labels)
|
||||
- [... with generic errors](#-with-generic-errors)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
- [License](#license)
|
||||
|
||||
|
|
@ -782,6 +783,58 @@ let report: miette::Report = MyError {
|
|||
|
||||
println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string()));
|
||||
```
|
||||
#### ... with generic errors
|
||||
|
||||
When tring to build more complex error types, it can often be useful to use generics.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
enum MyError<T> {
|
||||
#[error(transparent)]
|
||||
#[diagnostic(transparent)]
|
||||
Base(T),
|
||||
#[error("Some other error occured")]
|
||||
#[diagnostic(help = "See the manual.")]
|
||||
OtherError
|
||||
}
|
||||
```
|
||||
To enable this pattern, you can enable **the `perfect-derive` feature** on miette.
|
||||
This will add trait bounds on generics in the `Diagnostic` implementation, depending on how
|
||||
they are used inside the struct/enum.
|
||||
|
||||
This should work for all other attributes as well, like `#[label]` or `#[diagnostic_source]`.
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
|
||||
##### ⚠ Warning: (Small) Gotcha with the `#[related]` attribute
|
||||
|
||||
</summary>
|
||||
|
||||
Because of current lifetime constraints, only generic collection elements but not generic
|
||||
collections are currently supported, meaning the following works:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Some example error")]
|
||||
struct MyError<T> {
|
||||
#[related]
|
||||
related_errors: Vec<T>
|
||||
}
|
||||
```
|
||||
but the following does not:
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("Some example error")]
|
||||
struct MyError<T> {
|
||||
// See the difference here?
|
||||
// Note that the collection is general, and not
|
||||
// the elements inside the vec. This is **not** supported.
|
||||
#[related]
|
||||
related_errors: T // <- here
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### MSRV
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ repository = "https://github.com/zkat/miette"
|
|||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
perfect-derive = ["syn/extra-traits"]
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.83"
|
||||
quote = "1.0.35"
|
||||
|
|
|
|||
|
|
@ -56,13 +56,25 @@ impl Code {
|
|||
let code = &code.as_ref()?.0;
|
||||
Some(match fields {
|
||||
syn::Fields::Named(_) => {
|
||||
quote! { Self::#ident { .. } => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
quote! {
|
||||
Self::#ident { .. } => {
|
||||
std::option::Option::Some(std::boxed::Box::new(#code))
|
||||
},
|
||||
}
|
||||
}
|
||||
syn::Fields::Unnamed(_) => {
|
||||
quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
quote! {
|
||||
Self::#ident(..) => {
|
||||
std::option::Option::Some(std::boxed::Box::new(#code))
|
||||
},
|
||||
}
|
||||
}
|
||||
syn::Fields::Unit => {
|
||||
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
quote! {
|
||||
Self::#ident => {
|
||||
std::option::Option::Some(std::boxed::Box::new(#code))
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use crate::label::Labels;
|
|||
use crate::related::Related;
|
||||
use crate::severity::Severity;
|
||||
use crate::source_code::SourceCode;
|
||||
use crate::trait_bounds::TypeParamBoundStore;
|
||||
use crate::url::Url;
|
||||
|
||||
pub enum Diagnostic {
|
||||
|
|
@ -19,11 +20,13 @@ pub enum Diagnostic {
|
|||
ident: syn::Ident,
|
||||
fields: syn::Fields,
|
||||
args: DiagnosticDefArgs,
|
||||
bound_store: TypeParamBoundStore,
|
||||
},
|
||||
Enum {
|
||||
ident: syn::Ident,
|
||||
generics: syn::Generics,
|
||||
variants: Vec<DiagnosticDef>,
|
||||
bound_store: TypeParamBoundStore,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -71,12 +74,15 @@ pub struct DiagnosticConcreteArgs {
|
|||
}
|
||||
|
||||
impl DiagnosticConcreteArgs {
|
||||
fn for_fields(fields: &syn::Fields) -> Result<Self, syn::Error> {
|
||||
let labels = Labels::from_fields(fields)?;
|
||||
let source_code = SourceCode::from_fields(fields)?;
|
||||
let related = Related::from_fields(fields)?;
|
||||
fn for_fields(
|
||||
fields: &syn::Fields,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> Result<Self, syn::Error> {
|
||||
let labels = Labels::from_fields(fields, bounds_store)?;
|
||||
let source_code = SourceCode::from_fields(fields, bounds_store)?;
|
||||
let related = Related::from_fields(fields, bounds_store)?;
|
||||
let help = Help::from_fields(fields)?;
|
||||
let diagnostic_source = DiagnosticSource::from_fields(fields)?;
|
||||
let diagnostic_source = DiagnosticSource::from_fields(fields, bounds_store)?;
|
||||
Ok(DiagnosticConcreteArgs {
|
||||
code: None,
|
||||
help,
|
||||
|
|
@ -156,6 +162,7 @@ impl DiagnosticDefArgs {
|
|||
_ident: &syn::Ident,
|
||||
fields: &syn::Fields,
|
||||
attrs: &[&syn::Attribute],
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
allow_transparent: bool,
|
||||
) -> syn::Result<Self> {
|
||||
let mut errors = Vec::new();
|
||||
|
|
@ -166,7 +173,7 @@ impl DiagnosticDefArgs {
|
|||
attrs[0].parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated)
|
||||
{
|
||||
if matches!(args.first(), Some(DiagnosticArg::Transparent)) {
|
||||
let forward = Forward::for_transparent_field(fields)?;
|
||||
let forward = Forward::for_transparent_field(fields, bounds_store)?;
|
||||
return Ok(Self::Transparent(forward));
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +189,7 @@ impl DiagnosticDefArgs {
|
|||
matches!(d, DiagnosticArg::Transparent)
|
||||
}
|
||||
|
||||
let mut concrete = DiagnosticConcreteArgs::for_fields(fields)?;
|
||||
let mut concrete = DiagnosticConcreteArgs::for_fields(fields, bounds_store)?;
|
||||
for attr in attrs {
|
||||
let args =
|
||||
attr.parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated);
|
||||
|
|
@ -226,10 +233,13 @@ impl Diagnostic {
|
|||
.collect::<Vec<&syn::Attribute>>();
|
||||
Ok(match input.data {
|
||||
syn::Data::Struct(data_struct) => {
|
||||
let mut bounds_store = TypeParamBoundStore::new(&input.generics);
|
||||
|
||||
let args = DiagnosticDefArgs::parse(
|
||||
&input.ident,
|
||||
&data_struct.fields,
|
||||
&input_attrs,
|
||||
&mut bounds_store,
|
||||
true,
|
||||
)?;
|
||||
|
||||
|
|
@ -238,16 +248,23 @@ impl Diagnostic {
|
|||
ident: input.ident,
|
||||
generics: input.generics,
|
||||
args,
|
||||
bound_store: bounds_store,
|
||||
}
|
||||
}
|
||||
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
|
||||
let mut vars = Vec::new();
|
||||
let mut bound_store = TypeParamBoundStore::new(&input.generics);
|
||||
for var in variants {
|
||||
let mut variant_attrs = input_attrs.clone();
|
||||
variant_attrs
|
||||
.extend(var.attrs.iter().filter(|x| x.path().is_ident("diagnostic")));
|
||||
let args =
|
||||
DiagnosticDefArgs::parse(&var.ident, &var.fields, &variant_attrs, true)?;
|
||||
let args = DiagnosticDefArgs::parse(
|
||||
&var.ident,
|
||||
&var.fields,
|
||||
&variant_attrs,
|
||||
&mut bound_store,
|
||||
true,
|
||||
)?;
|
||||
vars.push(DiagnosticDef {
|
||||
ident: var.ident,
|
||||
fields: var.fields,
|
||||
|
|
@ -258,6 +275,7 @@ impl Diagnostic {
|
|||
ident: input.ident,
|
||||
generics: input.generics,
|
||||
variants: vars,
|
||||
bound_store,
|
||||
}
|
||||
}
|
||||
syn::Data::Union(_) => {
|
||||
|
|
@ -276,8 +294,11 @@ impl Diagnostic {
|
|||
fields,
|
||||
generics,
|
||||
args,
|
||||
bound_store,
|
||||
} => {
|
||||
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let where_clause = bound_store.add_to_where_clause(where_clause);
|
||||
|
||||
match args {
|
||||
DiagnosticDefArgs::Transparent(forward) => {
|
||||
let code_method = forward.gen_struct_method(WhichFn::Code);
|
||||
|
|
@ -291,7 +312,9 @@ impl Diagnostic {
|
|||
forward.gen_struct_method(WhichFn::DiagnosticSource);
|
||||
|
||||
quote! {
|
||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||
impl #impl_generics miette::Diagnostic
|
||||
for #ident #ty_generics
|
||||
#where_clause {
|
||||
#code_method
|
||||
#help_method
|
||||
#url_method
|
||||
|
|
@ -351,7 +374,9 @@ impl Diagnostic {
|
|||
.and_then(|x| x.gen_struct())
|
||||
.or_else(|| forward(WhichFn::DiagnosticSource));
|
||||
quote! {
|
||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||
impl #impl_generics miette::Diagnostic
|
||||
for #ident #ty_generics
|
||||
#where_clause {
|
||||
#code_body
|
||||
#help_body
|
||||
#sev_body
|
||||
|
|
@ -369,8 +394,11 @@ impl Diagnostic {
|
|||
ident,
|
||||
generics,
|
||||
variants,
|
||||
bound_store,
|
||||
} => {
|
||||
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let where_clause = bound_store.add_to_where_clause(where_clause);
|
||||
|
||||
let code_body = Code::gen_enum(variants);
|
||||
let help_body = Help::gen_enum(variants);
|
||||
let sev_body = Severity::gen_enum(variants);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use quote::quote;
|
|||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::forward::WhichFn;
|
||||
use crate::trait_bounds::TypeParamBoundStore;
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
utils::{display_pat_members, gen_all_variants_with},
|
||||
|
|
@ -11,17 +12,25 @@ use crate::{
|
|||
pub struct DiagnosticSource(syn::Member);
|
||||
|
||||
impl DiagnosticSource {
|
||||
pub(crate) fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
||||
pub(crate) fn from_fields(
|
||||
fields: &syn::Fields,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> syn::Result<Option<Self>> {
|
||||
match fields {
|
||||
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
||||
syn::Fields::Named(named) => {
|
||||
Self::from_fields_vec(named.named.iter().collect(), bounds_store)
|
||||
}
|
||||
syn::Fields::Unnamed(unnamed) => {
|
||||
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
||||
Self::from_fields_vec(unnamed.unnamed.iter().collect(), bounds_store)
|
||||
}
|
||||
syn::Fields::Unit => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
||||
fn from_fields_vec(
|
||||
fields: Vec<&syn::Field>,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> syn::Result<Option<Self>> {
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
for attr in &field.attrs {
|
||||
if attr.path().is_ident("diagnostic_source") {
|
||||
|
|
@ -33,6 +42,12 @@ impl DiagnosticSource {
|
|||
span: field.span(),
|
||||
})
|
||||
};
|
||||
|
||||
let ty = &field.ty;
|
||||
bounds_store.add_where_predicate(
|
||||
syn::parse_quote!(#ty: ::miette::Diagnostic + 'static),
|
||||
);
|
||||
|
||||
return Ok(Some(DiagnosticSource(diagnostic_source)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ use syn::{
|
|||
spanned::Spanned,
|
||||
};
|
||||
|
||||
use crate::trait_bounds::TypeParamBoundStore;
|
||||
|
||||
pub enum Forward {
|
||||
Unnamed(usize),
|
||||
Named(syn::Ident),
|
||||
|
|
@ -70,10 +72,14 @@ impl WhichFn {
|
|||
fn severity(&self) -> std::option::Option<miette::Severity>
|
||||
},
|
||||
Self::Related => quote! {
|
||||
fn related(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &dyn miette::Diagnostic> + '_>>
|
||||
fn related(&self) -> std::option::Option<
|
||||
std::boxed::Box<dyn std::iter::Iterator<Item = &dyn miette::Diagnostic> + '_>
|
||||
>
|
||||
},
|
||||
Self::Labels => quote! {
|
||||
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>>
|
||||
fn labels(&self) -> std::option::Option<
|
||||
std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>
|
||||
>
|
||||
},
|
||||
Self::SourceCode => quote! {
|
||||
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode>
|
||||
|
|
@ -90,7 +96,10 @@ impl WhichFn {
|
|||
}
|
||||
|
||||
impl Forward {
|
||||
pub fn for_transparent_field(fields: &syn::Fields) -> syn::Result<Self> {
|
||||
pub fn for_transparent_field(
|
||||
fields: &syn::Fields,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> syn::Result<Self> {
|
||||
let make_err = || {
|
||||
syn::Error::new(
|
||||
fields.span(),
|
||||
|
|
@ -108,12 +117,22 @@ impl Forward {
|
|||
.ident
|
||||
.clone()
|
||||
.unwrap_or_else(|| format_ident!("unnamed"));
|
||||
|
||||
let ty = &field.ty;
|
||||
bounds_store
|
||||
.add_where_predicate(syn::parse_quote! {#ty: ::miette::Diagnostic + 'static});
|
||||
Ok(Self::Named(field_name))
|
||||
}
|
||||
syn::Fields::Unnamed(unnamed) => {
|
||||
if unnamed.unnamed.iter().len() != 1 {
|
||||
let mut iter = unnamed.unnamed.iter();
|
||||
let field = iter.next().ok_or_else(make_err)?;
|
||||
if iter.next().is_some() {
|
||||
return Err(make_err());
|
||||
}
|
||||
|
||||
let ty = &field.ty;
|
||||
bounds_store
|
||||
.add_where_predicate(syn::parse_quote! {#ty: ::miette::Diagnostic + 'static});
|
||||
Ok(Self::Unnamed(0))
|
||||
}
|
||||
_ => Err(syn::Error::new(
|
||||
|
|
|
|||
|
|
@ -94,7 +94,9 @@ impl Help {
|
|||
Help::Display(display) => {
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
Some(quote! {
|
||||
Self::#ident #display_pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
|
||||
Self::#ident #display_pat => {
|
||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||
},
|
||||
})
|
||||
}
|
||||
Help::Field(member, ty) => {
|
||||
|
|
@ -123,7 +125,9 @@ impl Help {
|
|||
Help::Display(display) => {
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
Some(quote! {
|
||||
fn help(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
|
||||
fn help(&self) -> std::option::Option<
|
||||
std::boxed::Box<dyn std::fmt::Display + '_>
|
||||
> {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
let Self #display_pat = self;
|
||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||
|
|
@ -133,7 +137,9 @@ impl Help {
|
|||
Help::Field(member, ty) => {
|
||||
let var = quote! { __miette_internal_var };
|
||||
Some(quote! {
|
||||
fn help(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
|
||||
fn help(&self) -> std::option::Option<
|
||||
std::boxed::Box<dyn std::fmt::Display + '_>
|
||||
> {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
let Self #display_pat = self;
|
||||
use miette::macro_helpers::ToOption;
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ use syn::{
|
|||
parenthesized,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
Token,
|
||||
Lifetime, Token,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
fmt::{self, Display},
|
||||
forward::WhichFn,
|
||||
utils::{display_pat_members, gen_all_variants_with},
|
||||
trait_bounds::TypeParamBoundStore,
|
||||
utils::{display_pat_members, extract_option, gen_all_variants_with},
|
||||
};
|
||||
|
||||
pub struct Labels(Vec<Label>);
|
||||
|
|
@ -101,22 +102,31 @@ impl Parse for LabelAttr {
|
|||
} else {
|
||||
(LabelType::Default, None)
|
||||
};
|
||||
|
||||
Ok(LabelAttr { label, lbl_ty })
|
||||
}
|
||||
}
|
||||
|
||||
impl Labels {
|
||||
pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
||||
pub fn from_fields(
|
||||
fields: &syn::Fields,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> syn::Result<Option<Self>> {
|
||||
match fields {
|
||||
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
||||
syn::Fields::Named(named) => {
|
||||
Self::from_fields_vec(named.named.iter().collect(), bounds_store)
|
||||
}
|
||||
syn::Fields::Unnamed(unnamed) => {
|
||||
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
||||
Self::from_fields_vec(unnamed.unnamed.iter().collect(), bounds_store)
|
||||
}
|
||||
syn::Fields::Unit => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
||||
fn from_fields_vec(
|
||||
fields: Vec<&syn::Field>,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> syn::Result<Option<Self>> {
|
||||
let mut labels = Vec::new();
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
for attr in &field.attrs {
|
||||
|
|
@ -144,6 +154,36 @@ impl Labels {
|
|||
));
|
||||
}
|
||||
|
||||
match lbl_ty {
|
||||
LabelType::Default | LabelType::Primary => {
|
||||
let option_ty = extract_option(&field.ty).unwrap_or(&field.ty);
|
||||
bounds_store.extend_where_predicates(syn::parse_quote!{
|
||||
#option_ty: ::std::borrow::ToOwned,
|
||||
<#option_ty as ::std::borrow::ToOwned>::Owned: ::std::convert::Into<::miette::SourceSpan>
|
||||
});
|
||||
}
|
||||
|
||||
LabelType::Collection => {
|
||||
let ty = &field.ty;
|
||||
let lt: Lifetime = syn::parse_quote!('__miette_internal_lt);
|
||||
bounds_store.extend_where_predicates(syn::parse_quote!{
|
||||
for<#lt> &#lt #ty: ::std::iter::IntoIterator,
|
||||
for<#lt> <&#lt #ty as ::std::iter::IntoIterator>::Item: ::std::ops::Deref,
|
||||
for<#lt> <
|
||||
<&#lt #ty as ::std::iter::IntoIterator>::Item
|
||||
as ::std::ops::Deref
|
||||
>::Target : ::std::borrow::ToOwned,
|
||||
for<#lt> <
|
||||
<
|
||||
<&#lt #ty as ::std::iter::IntoIterator>::Item
|
||||
as ::std::ops::Deref
|
||||
>::Target
|
||||
as ::std::borrow::ToOwned
|
||||
>::Owned: ::std::convert::Into<::miette::SourceSpan>
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
labels.push(Label {
|
||||
label,
|
||||
span,
|
||||
|
|
@ -187,10 +227,15 @@ impl Labels {
|
|||
|
||||
Some(quote! {
|
||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||
.map(|#var| #ctor(
|
||||
.as_ref()
|
||||
.map(|#var| {
|
||||
use ::std::borrow::ToOwned;
|
||||
|
||||
#ctor(
|
||||
#display,
|
||||
#var.clone(),
|
||||
))
|
||||
(*#var).to_owned(),
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
let collections_chain = self.0.iter().filter_map(|label| {
|
||||
|
|
@ -209,12 +254,14 @@ impl Labels {
|
|||
} else {
|
||||
quote! { std::option::Option::None }
|
||||
};
|
||||
|
||||
Some(quote! {
|
||||
.chain({
|
||||
let display = #display;
|
||||
self.#span.iter().map(move |span| {
|
||||
::std::iter::IntoIterator::into_iter(&self.#span).map(move |span| {
|
||||
use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan};
|
||||
let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone());
|
||||
use ::std::borrow::ToOwned;
|
||||
let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.to_owned());
|
||||
if display.is_some() && labeled_span.label().is_none() {
|
||||
labeled_span.set_label(display.clone())
|
||||
}
|
||||
|
|
@ -226,7 +273,9 @@ impl Labels {
|
|||
|
||||
Some(quote! {
|
||||
#[allow(unused_variables)]
|
||||
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||
fn labels(&self) -> std::option::Option<
|
||||
std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>
|
||||
> {
|
||||
use miette::macro_helpers::ToOption;
|
||||
let Self #display_pat = self;
|
||||
|
||||
|
|
@ -236,7 +285,10 @@ impl Labels {
|
|||
.into_iter()
|
||||
#(#collections_chain)*;
|
||||
|
||||
std::option::Option::Some(Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
|
||||
std::option::Option::Some(Box::new(
|
||||
labels_iter
|
||||
.filter_map(|x| x)
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -249,7 +301,12 @@ impl Labels {
|
|||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
labels.as_ref().and_then(|labels| {
|
||||
let variant_labels = labels.0.iter().filter_map(|label| {
|
||||
let Label { span, label, ty, lbl_ty } = label;
|
||||
let Label {
|
||||
span,
|
||||
label,
|
||||
ty,
|
||||
lbl_ty,
|
||||
} = label;
|
||||
if *lbl_ty == LabelType::Collection {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -274,14 +331,24 @@ impl Labels {
|
|||
|
||||
Some(quote! {
|
||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||
.map(|#var| #ctor(
|
||||
.as_ref()
|
||||
.map(|#var| {
|
||||
use ::std::borrow::ToOwned;
|
||||
|
||||
#ctor(
|
||||
#display,
|
||||
#var.clone(),
|
||||
))
|
||||
(*#var).to_owned(),
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
let collections_chain = labels.0.iter().filter_map(|label| {
|
||||
let Label { span, label, ty: _, lbl_ty } = label;
|
||||
let Label {
|
||||
span,
|
||||
label,
|
||||
ty: _,
|
||||
lbl_ty,
|
||||
} = label;
|
||||
if *lbl_ty != LabelType::Collection {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -300,9 +367,12 @@ impl Labels {
|
|||
Some(quote! {
|
||||
.chain({
|
||||
let display = #display;
|
||||
#field.iter().map(move |span| {
|
||||
::std::iter::IntoIterator::into_iter(#field).map(move |span| {
|
||||
use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan};
|
||||
let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone());
|
||||
use ::std::borrow::ToOwned;
|
||||
let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(
|
||||
span.to_owned()
|
||||
);
|
||||
if display.is_some() && labeled_span.label().is_none() {
|
||||
labeled_span.set_label(display.clone());
|
||||
}
|
||||
|
|
@ -322,7 +392,10 @@ impl Labels {
|
|||
]
|
||||
.into_iter()
|
||||
#(#collections_chain)*;
|
||||
std::option::Option::Some(std::boxed::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
|
||||
std::option::Option::Some(std::boxed::Box::new(
|
||||
labels_iter
|
||||
.filter_map(|x| x)
|
||||
))
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ mod label;
|
|||
mod related;
|
||||
mod severity;
|
||||
mod source_code;
|
||||
mod trait_bounds;
|
||||
mod url;
|
||||
mod utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,23 +5,32 @@ use syn::spanned::Spanned;
|
|||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
forward::WhichFn,
|
||||
trait_bounds::TypeParamBoundStore,
|
||||
utils::{display_pat_members, gen_all_variants_with},
|
||||
};
|
||||
|
||||
pub struct Related(syn::Member);
|
||||
|
||||
impl Related {
|
||||
pub(crate) fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
||||
pub(crate) fn from_fields(
|
||||
fields: &syn::Fields,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> syn::Result<Option<Self>> {
|
||||
match fields {
|
||||
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
||||
syn::Fields::Named(named) => {
|
||||
Self::from_fields_vec(named.named.iter().collect(), bounds_store)
|
||||
}
|
||||
syn::Fields::Unnamed(unnamed) => {
|
||||
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
||||
Self::from_fields_vec(unnamed.unnamed.iter().collect(), bounds_store)
|
||||
}
|
||||
syn::Fields::Unit => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
||||
fn from_fields_vec(
|
||||
fields: Vec<&syn::Field>,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> syn::Result<Option<Self>> {
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
for attr in &field.attrs {
|
||||
if attr.path().is_ident("related") {
|
||||
|
|
@ -33,6 +42,17 @@ impl Related {
|
|||
span: field.span(),
|
||||
})
|
||||
};
|
||||
// this is somewhat hacky and only supports concrete types for the #[related] type
|
||||
// ittself but supports generics for the arguments, i.e. Vec<T> where T is generic.
|
||||
//
|
||||
// I think that this is a current limitation of the design of the Diagnostic trait,
|
||||
// since we'd need bounds on the method and we can't do that (to refer to the lifetime)
|
||||
//
|
||||
// Someone smarter than me might be able to figure out a better solution (?)
|
||||
let ty = &field.ty;
|
||||
bounds_store.add_where_predicate(syn::parse_quote!(
|
||||
<#ty as ::std::iter::IntoIterator>::Item: ::miette::Diagnostic + 'static
|
||||
));
|
||||
return Ok(Some(Related(related)));
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +88,9 @@ impl Related {
|
|||
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||
let rel = &self.0;
|
||||
Some(quote! {
|
||||
fn related<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
|
||||
fn related<'a>(&'a self) -> std::option::Option<
|
||||
std::boxed::Box<dyn std::iter::Iterator<Item = &'a dyn miette::Diagnostic> + 'a>
|
||||
> {
|
||||
use ::core::borrow::Borrow;
|
||||
std::option::Option::Some(std::boxed::Box::new(
|
||||
self.#rel.iter().map(|x| -> &(dyn miette::Diagnostic) { &*x.borrow() })
|
||||
|
|
|
|||
|
|
@ -71,9 +71,11 @@ impl Severity {
|
|||
syn::Fields::Unnamed(_) => quote! { (..) },
|
||||
syn::Fields::Unit => quote! {},
|
||||
};
|
||||
Some(
|
||||
quote! { Self::#ident #fields => std::option::Option::Some(miette::Severity::#severity), },
|
||||
)
|
||||
Some(quote! {
|
||||
Self::#ident #fields => {
|
||||
std::option::Option::Some(miette::Severity::#severity)
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use syn::spanned::Spanned;
|
|||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
forward::WhichFn,
|
||||
utils::{display_pat_members, gen_all_variants_with},
|
||||
trait_bounds::TypeParamBoundStore,
|
||||
utils::{display_pat_members, extract_option, gen_all_variants_with},
|
||||
};
|
||||
|
||||
pub struct SourceCode {
|
||||
|
|
@ -14,32 +15,33 @@ pub struct SourceCode {
|
|||
}
|
||||
|
||||
impl SourceCode {
|
||||
pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
||||
pub fn from_fields(
|
||||
fields: &syn::Fields,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> syn::Result<Option<Self>> {
|
||||
match fields {
|
||||
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
||||
syn::Fields::Named(named) => {
|
||||
Self::from_fields_vec(named.named.iter().collect(), bounds_store)
|
||||
}
|
||||
syn::Fields::Unnamed(unnamed) => {
|
||||
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
||||
Self::from_fields_vec(unnamed.unnamed.iter().collect(), bounds_store)
|
||||
}
|
||||
syn::Fields::Unit => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
||||
fn from_fields_vec(
|
||||
fields: Vec<&syn::Field>,
|
||||
bounds_store: &mut TypeParamBoundStore,
|
||||
) -> syn::Result<Option<Self>> {
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
for attr in &field.attrs {
|
||||
if attr.path().is_ident("source_code") {
|
||||
let is_option = if let syn::Type::Path(syn::TypePath {
|
||||
path: syn::Path { segments, .. },
|
||||
..
|
||||
}) = &field.ty
|
||||
{
|
||||
segments
|
||||
.last()
|
||||
.map(|seg| seg.ident == "Option")
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let is_option = extract_option(&field.ty);
|
||||
|
||||
let code_ty = is_option.unwrap_or(&field.ty);
|
||||
bounds_store
|
||||
.add_where_predicate(syn::parse_quote!(#code_ty: ::miette::SourceCode));
|
||||
|
||||
let source_code = if let Some(ident) = field.ident.clone() {
|
||||
syn::Member::Named(ident)
|
||||
|
|
@ -51,7 +53,7 @@ impl SourceCode {
|
|||
};
|
||||
return Ok(Some(SourceCode {
|
||||
source_code,
|
||||
is_option,
|
||||
is_option: is_option.is_some(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
#![allow(dead_code)]
|
||||
use syn::{punctuated::Punctuated, Generics, PredicateType, Token, WhereClause, WherePredicate};
|
||||
|
||||
// Mock for when perfect-derive is not enabled,
|
||||
// this should be completely optimized away and enables
|
||||
// easily switching on/off the perfect-derive feature without
|
||||
// needing to modify any other code.
|
||||
pub struct TypeParamBoundStore;
|
||||
|
||||
impl TypeParamBoundStore {
|
||||
pub fn new(_: &Generics) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn add_predicate(&mut self, _: PredicateType) {}
|
||||
|
||||
pub fn add_where_predicate(&mut self, _: WherePredicate) {}
|
||||
|
||||
pub fn extend_where_predicates(&mut self, _: Punctuated<WherePredicate, Token![,]>) {}
|
||||
|
||||
pub fn add_to_where_clause(&self, where_clause: Option<&WhereClause>) -> Option<WhereClause> {
|
||||
where_clause.cloned()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#[cfg(not(feature = "perfect-derive"))]
|
||||
mod mock_store;
|
||||
#[cfg(not(feature = "perfect-derive"))]
|
||||
pub use mock_store::TypeParamBoundStore;
|
||||
|
||||
#[cfg(feature = "perfect-derive")]
|
||||
mod store;
|
||||
#[cfg(feature = "perfect-derive")]
|
||||
pub use store::TypeParamBoundStore;
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
iter::once,
|
||||
};
|
||||
|
||||
use proc_macro2::Span;
|
||||
use syn::{
|
||||
punctuated::Punctuated, AngleBracketedGenericArguments, AssocType, BoundLifetimes,
|
||||
GenericArgument, GenericParam, Generics, ParenthesizedGenericArguments, PathArguments,
|
||||
PredicateType, ReturnType, Token, Type, TypeArray, TypeGroup, TypeParamBound, TypeParen,
|
||||
TypePath, TypePtr, TypeReference, TypeSlice, TypeTuple, WhereClause, WherePredicate,
|
||||
};
|
||||
|
||||
// Potential improvement, although idk if this actually ends up
|
||||
// mattering (if it is a messurable improvement) is to switch this to something like FxHashMap
|
||||
// like the rustc compiler uses internally, although we should benchmark this and can always do it later
|
||||
// since it is easy enough to change.
|
||||
#[cfg(feature = "perfect-derive")]
|
||||
pub struct TypeParamBoundStore(HashMap<(Option<BoundLifetimes>, Type), HashSet<TypeParamBound>>);
|
||||
|
||||
#[cfg(feature = "perfect-derive")]
|
||||
impl TypeParamBoundStore {
|
||||
/// Creates a new TraitBoundStore, filling it with some generics which are used to heuristically remove trivial bounds.
|
||||
///
|
||||
/// Note that it is essential that all relevant generics are actually passed here, since if they aren't bounds which are required might be heuristically removed.
|
||||
pub fn new(generics: &Generics) -> Self {
|
||||
let hash_map = generics
|
||||
.params
|
||||
.iter()
|
||||
.filter_map(|param| match param {
|
||||
GenericParam::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
})
|
||||
.map(|param| {
|
||||
let ident = ¶m.ident;
|
||||
Type::Path(syn::parse_quote!(#ident))
|
||||
})
|
||||
.map(|ty| ((None, ty), Default::default()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
Self(hash_map)
|
||||
}
|
||||
|
||||
/// Checks heuristically if `type` is using any generic type inside it.
|
||||
///
|
||||
/// This is guaranteed to never false-negative but might
|
||||
/// false-positive if checking exhaustively would be expensive or
|
||||
/// an unexpected case is encountered which this can't handle.
|
||||
///
|
||||
/// # Returns
|
||||
/// Option with a simplified type if determined to be dependant, none otherwise
|
||||
fn generic_usage_heuristics(&self, mut r#type: Type) -> Option<Type> {
|
||||
// in theory we could skip all this logic and just allow trivial bounds but that would add redundant trait bounds
|
||||
// to the derived impl - would be another choice to make. I choose to filter as much as possible so that we don't
|
||||
// introduce unneccessary bounds.
|
||||
|
||||
// this reduces the type down as much as possible to remove unneeded groups.
|
||||
let original_type = loop {
|
||||
match r#type {
|
||||
Type::Paren(TypeParen { elem, .. }) => r#type = *elem,
|
||||
Type::Group(TypeGroup { elem, .. }) => r#type = *elem,
|
||||
x => break x,
|
||||
}
|
||||
};
|
||||
|
||||
let mut depends_on_generic = false;
|
||||
|
||||
// max depth to check, after which we'll just add the (maybe redundant) bound anyways.
|
||||
// this is a tradeoff between filtering speed and compiler speed so I'll keep it
|
||||
// reasonably low for now, since I assume the compiler is better optimized for more complex
|
||||
// checks.
|
||||
let max_depth = 8;
|
||||
|
||||
let mut to_check_queue: VecDeque<(&Type, usize)> = VecDeque::new();
|
||||
to_check_queue.push_back((&original_type, 0));
|
||||
|
||||
while !depends_on_generic {
|
||||
// this needs to be like this cuz if-let-chains aren't supported yet
|
||||
let Some((elem, current_depth)) = to_check_queue.pop_front() else {
|
||||
break;
|
||||
};
|
||||
|
||||
// if we exceed the max depth we just assume it depends on the generic and let the compiler check it
|
||||
if current_depth > max_depth {
|
||||
depends_on_generic = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// the map contains types that we know depend on generics so we can just short circuit
|
||||
//
|
||||
// this is also the "bottom" check since we add the generics themselves to the map when
|
||||
// constructing self
|
||||
if self.0.contains_key(&(None, elem.clone())) {
|
||||
depends_on_generic = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// basically go through the type and add all referenced types inside it to the check queue
|
||||
match elem {
|
||||
Type::Group(_) => unreachable!("This is unwrapped above"),
|
||||
Type::Paren(_) => unreachable!("This is unwrapped above"),
|
||||
// function pointer's can never implement the required trait bounds anyways so we just accept the errors
|
||||
Type::BareFn(_) => return None,
|
||||
// impl trait types aren't allowed from struct/enum definitions anyways so we can just ignore them
|
||||
Type::ImplTrait(_) => return None,
|
||||
// infered types aren't allowed either
|
||||
Type::Infer(_) => return None,
|
||||
// macros are opaque to us and i don't really know how to properly implement this.
|
||||
// we could in theory I think introduce a type alias and use that instead but honestly
|
||||
// type macros are such a niche usecase especially in combination with a generic,
|
||||
// I would say we should just recommend to implement
|
||||
// the trait manually, as such we just accept the error if any occurs (this still allows using macros when they
|
||||
// return concrete types which don't depend on any generic or when the generic doesn't affect the
|
||||
// required trait implementation)
|
||||
Type::Macro(_) => return None,
|
||||
// trait objects which depend on a generic inside them seem like very much a hassle to implement so i'll ignore
|
||||
// them for now, if the need arises we could support that in a future pr maybe?
|
||||
//
|
||||
// this again doesn't restrict the usage of trait objects which implement the required traits regardless of the generics.
|
||||
Type::TraitObject(_) => return None,
|
||||
// Well never is never and never never.
|
||||
Type::Never(_) => return None,
|
||||
Type::Array(TypeArray { elem, .. })
|
||||
| Type::Ptr(TypePtr { elem, .. })
|
||||
| Type::Reference(TypeReference { elem, .. })
|
||||
| Type::Slice(TypeSlice { elem, .. }) => {
|
||||
to_check_queue.push_back((&**elem, current_depth + 1));
|
||||
}
|
||||
Type::Path(TypePath { qself, path }) => {
|
||||
if let Some(qself) = qself {
|
||||
to_check_queue.push_back((&qself.ty, current_depth + 1));
|
||||
}
|
||||
|
||||
for segment in &path.segments {
|
||||
match &segment.arguments {
|
||||
PathArguments::None => {}
|
||||
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
|
||||
args,
|
||||
..
|
||||
}) => {
|
||||
for argument in args {
|
||||
match argument {
|
||||
GenericArgument::Type(ty)
|
||||
| GenericArgument::AssocType(AssocType { ty, .. }) => {
|
||||
to_check_queue.push_back((ty, current_depth + 1));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
PathArguments::Parenthesized(ParenthesizedGenericArguments {
|
||||
inputs,
|
||||
output,
|
||||
..
|
||||
}) => {
|
||||
for inp in inputs {
|
||||
to_check_queue.push_back((inp, current_depth + 1));
|
||||
}
|
||||
|
||||
if let ReturnType::Type(_, ty) = output {
|
||||
to_check_queue.push_back((ty, current_depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::Tuple(TypeTuple { elems, .. }) => {
|
||||
for elem in elems {
|
||||
to_check_queue.push_back((elem, current_depth + 1));
|
||||
}
|
||||
}
|
||||
// we can't really handle verbatim so we just assume it depends on the generics
|
||||
Type::Verbatim(_) => depends_on_generic = true,
|
||||
_ => depends_on_generic = true,
|
||||
}
|
||||
}
|
||||
|
||||
depends_on_generic.then_some(original_type)
|
||||
}
|
||||
|
||||
pub fn add_predicate(
|
||||
&mut self,
|
||||
PredicateType {
|
||||
lifetimes,
|
||||
bounded_ty,
|
||||
colon_token: _,
|
||||
bounds,
|
||||
}: PredicateType,
|
||||
) {
|
||||
let Some(bounded_ty) = self.generic_usage_heuristics(bounded_ty) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.0
|
||||
.entry((lifetimes, bounded_ty))
|
||||
.or_default()
|
||||
.extend(bounds);
|
||||
}
|
||||
|
||||
// Since syn for some reason doesn't implement `Parse` for `PredicateType`
|
||||
// this method is meant for ease of use with `syn::parse_quote!`.
|
||||
pub fn add_where_predicate(&mut self, predicate: WherePredicate) {
|
||||
let WherePredicate::Type(ty) = predicate else {
|
||||
unimplemented!("Only type predicates are supported");
|
||||
};
|
||||
|
||||
self.add_predicate(ty);
|
||||
}
|
||||
|
||||
pub fn extend_where_predicates(&mut self, predicates: Punctuated<WherePredicate, Token![,]>) {
|
||||
for predicate in predicates {
|
||||
self.add_where_predicate(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_to_where_clause(&self, where_clause: Option<&WhereClause>) -> Option<WhereClause> {
|
||||
let predicates = self
|
||||
.0
|
||||
.iter()
|
||||
.filter(|(_, bounds)| !bounds.is_empty())
|
||||
.map(|(a, b)| (a.clone(), b.clone()))
|
||||
.map(|((lifetimes, bounded_ty), bounds)| {
|
||||
WherePredicate::Type(PredicateType {
|
||||
lifetimes,
|
||||
bounded_ty,
|
||||
colon_token: Token),
|
||||
bounds: bounds.into_iter().collect(),
|
||||
})
|
||||
})
|
||||
.peekable();
|
||||
|
||||
// de-duplicate elements newly added and within existing where clause
|
||||
let predicates = predicates
|
||||
.chain(
|
||||
where_clause
|
||||
.into_iter()
|
||||
.flat_map(|where_clause| where_clause.predicates.clone()),
|
||||
)
|
||||
.chain(once(syn::parse_quote!(Self: ::std::error::Error)))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
Some(WhereClause {
|
||||
where_token: Token),
|
||||
predicates: predicates.into_iter().collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -96,7 +96,9 @@ impl Url {
|
|||
}
|
||||
};
|
||||
Some(quote! {
|
||||
Self::#ident #pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
|
||||
Self::#ident #pat => {
|
||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{spanned::Spanned, AngleBracketedGenericArguments, GenericArgument, PathArguments, Type};
|
||||
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
|
|
@ -104,3 +104,36 @@ impl Display {
|
|||
(fmt, args)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract the type of a (presumed) option type, returning the extracted type if it suceeded.
|
||||
pub fn extract_option(r#type: &Type) -> Option<&Type> {
|
||||
let syn::Type::Path(syn::TypePath {
|
||||
path: syn::Path { segments, .. },
|
||||
..
|
||||
}) = r#type
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let last_segment = segments.last()?;
|
||||
|
||||
if last_segment.ident != "Option" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) =
|
||||
&last_segment.arguments
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if args.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(GenericArgument::Type(ty)) = args.first() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(ty)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1401,13 +1401,17 @@ impl Line {
|
|||
/// text on this line
|
||||
fn span_applies(&self, span: &FancySpan) -> bool {
|
||||
let spanlen = if span.len() == 0 { 1 } else { span.len() };
|
||||
// Span starts in this line
|
||||
|
||||
(span.offset() >= self.offset && span.offset() < self.offset + self.length)
|
||||
// Span passes through this line
|
||||
|| (span.offset() < self.offset && span.offset() + spanlen > self.offset + self.length) //todo
|
||||
// Span ends on this line
|
||||
|| (span.offset() + spanlen > self.offset && span.offset() + spanlen <= self.offset + self.length)
|
||||
let span_starts_this_line =
|
||||
span.offset() >= self.offset && span.offset() < self.offset + self.length;
|
||||
|
||||
let span_passes_through_this_line =
|
||||
span.offset() < self.offset && span.offset() + spanlen > self.offset + self.length;
|
||||
|
||||
let span_ends_on_this_line = span.offset() + spanlen > self.offset
|
||||
&& span.offset() + spanlen <= self.offset + self.length;
|
||||
|
||||
span_starts_this_line || span_passes_through_this_line || span_ends_on_this_line
|
||||
}
|
||||
|
||||
/// Returns whether `span` should be visible on this line in the gutter (so this excludes spans
|
||||
|
|
|
|||
54
src/lib.rs
54
src/lib.rs
|
|
@ -53,6 +53,7 @@
|
|||
//! - [... syntax highlighting](#-syntax-highlighting)
|
||||
//! - [... primary label](#-primary-label)
|
||||
//! - [... collection of labels](#-collection-of-labels)
|
||||
//! - [... with generic errors](#-with-generic-errors)
|
||||
//! - [Acknowledgements](#acknowledgements)
|
||||
//! - [License](#license)
|
||||
//!
|
||||
|
|
@ -783,6 +784,59 @@
|
|||
//!
|
||||
//! println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string()));
|
||||
//! ```
|
||||
//! ### ... with generic errors
|
||||
//!
|
||||
//! When tring to build more complex error types, it can often be useful to use generics.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! enum MyError<T> {
|
||||
//! #[error(transparent)]
|
||||
//! #[diagnostic(transparent)]
|
||||
//! Base(T),
|
||||
//! #[error("Some other error occured")]
|
||||
//! #[diagnostic(help = "See the manual.")]
|
||||
//! OtherError
|
||||
//! }
|
||||
//! ```
|
||||
//! To enable this pattern, you can enable **the `perfect-derive` feature** on miette.
|
||||
//! This will add trait bounds on generics in the `Diagnostic` implementation, depending on how
|
||||
//! they are used inside the struct/enum.
|
||||
//!
|
||||
//! This should work for all other attributes as well, like `#[label]` or `#[diagnostic_source]`.
|
||||
//!
|
||||
//! <details>
|
||||
//! <summary>
|
||||
//!
|
||||
//! #### ⚠ Warning: (Small) Gotcha with the `#[related]` attribute
|
||||
//!
|
||||
//! </summary>
|
||||
//!
|
||||
//! Because of current lifetime constraints, only generic collection elements but not generic
|
||||
//! collections are currently supported, meaning the following works:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("Some example error")]
|
||||
//! struct MyError<T> {
|
||||
//! #[related]
|
||||
//! related_errors: Vec<T>
|
||||
//! }
|
||||
//! ```
|
||||
//! but the following does not:
|
||||
//! ```rust,ignore
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("Some example error")]
|
||||
//! struct MyError<T> {
|
||||
//! // See the difference here?
|
||||
//! // Note that the collection is general, and not
|
||||
//! // the elements inside the vec. This is **not** supported.
|
||||
//! #[related]
|
||||
//! related_errors: T // <- here
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
//! </details>
|
||||
//!
|
||||
//! ## MSRV
|
||||
//!
|
||||
|
|
|
|||
|
|
@ -217,6 +217,8 @@ fn fmt_help() {
|
|||
#[diagnostic(code(foo::x), help("{} x {len} x {:?}", 1, "2"))]
|
||||
Y { len: usize },
|
||||
|
||||
// for some reason rust analyzer has a false positive with the self = self in the format!
|
||||
// here but it compiles and tests just fine. (02/02/2025)
|
||||
#[diagnostic(code(foo::x), help("{} x {self:?} x {:?}", 1, "2"))]
|
||||
Z,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,3 +145,143 @@ fn attr_not_required() {
|
|||
let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize);
|
||||
assert_eq!(err_span, expectation);
|
||||
}
|
||||
|
||||
// Tests for the feature = "perfect-derive".
|
||||
fn assert_impl_diagnostic<T: Diagnostic>() {}
|
||||
|
||||
#[test]
|
||||
fn transparent_generic() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
enum Combined<T> {
|
||||
#[error(transparent)]
|
||||
#[diagnostic(transparent)]
|
||||
Other(T),
|
||||
#[error("foo")]
|
||||
Custom,
|
||||
}
|
||||
|
||||
std::hint::black_box(Combined::<i32>::Other(1));
|
||||
std::hint::black_box(Combined::<i32>::Custom);
|
||||
|
||||
assert_impl_diagnostic::<Combined<miette::MietteDiagnostic>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_label() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("foo")]
|
||||
struct Combined<T> {
|
||||
#[label]
|
||||
label: T,
|
||||
}
|
||||
|
||||
assert_impl_diagnostic::<Combined<SourceSpan>>();
|
||||
assert_impl_diagnostic::<Combined<(usize, usize)>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_source_code() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("foo")]
|
||||
struct Combined<T> {
|
||||
#[source_code]
|
||||
label: T,
|
||||
}
|
||||
|
||||
assert_impl_diagnostic::<Combined<String>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_optional_source_code() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("foo")]
|
||||
struct Combined<T> {
|
||||
#[source_code]
|
||||
label: Option<T>,
|
||||
}
|
||||
|
||||
assert_impl_diagnostic::<Combined<String>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_label_primary() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("foo")]
|
||||
struct Combined<T> {
|
||||
#[label(primary)]
|
||||
label: T,
|
||||
}
|
||||
|
||||
assert_impl_diagnostic::<Combined<SourceSpan>>();
|
||||
assert_impl_diagnostic::<Combined<(usize, usize)>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_label_collection() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("foo")]
|
||||
struct Combined<T> {
|
||||
#[label(collection)]
|
||||
label: Vec<T>,
|
||||
}
|
||||
|
||||
assert_impl_diagnostic::<Combined<SourceSpan>>();
|
||||
assert_impl_diagnostic::<Combined<(usize, usize)>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_label_generic_collection() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("foo")]
|
||||
struct Combined<T> {
|
||||
#[label(collection)]
|
||||
label: T,
|
||||
}
|
||||
|
||||
assert_impl_diagnostic::<Combined<Vec<SourceSpan>>>();
|
||||
assert_impl_diagnostic::<Combined<Vec<(usize, usize)>>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_related() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("foo")]
|
||||
struct Combined<T> {
|
||||
#[related]
|
||||
label: Vec<T>,
|
||||
}
|
||||
|
||||
assert_impl_diagnostic::<Combined<miette::MietteDiagnostic>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_diagnostic_source() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
enum Combined<T> {
|
||||
#[error(transparent)]
|
||||
Other(#[diagnostic_source] T),
|
||||
#[error("foo")]
|
||||
Custom,
|
||||
}
|
||||
|
||||
std::hint::black_box(Combined::<i32>::Other(1));
|
||||
std::hint::black_box(Combined::<i32>::Custom);
|
||||
|
||||
assert_impl_diagnostic::<Combined<miette::MietteDiagnostic>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_not_influencing_default() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
enum Combined<T> {
|
||||
#[error("bar")]
|
||||
Other(T),
|
||||
#[error("foo")]
|
||||
Custom,
|
||||
}
|
||||
|
||||
std::hint::black_box(Combined::<i32>::Other(1));
|
||||
std::hint::black_box(Combined::<i32>::Custom);
|
||||
|
||||
assert_impl_diagnostic::<Combined<i32>>();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue