mirror of https://github.com/zkat/miette.git
Iron out some bugs
Signed-off-by: Justus Fluegel <justusfluegel@gmail.com> Signed-off-by: Justus Flügel <justusfluegel@gmail.com>
This commit is contained in:
parent
1a3a4c224a
commit
89d30a3b37
|
|
@ -156,11 +156,11 @@ impl Labels {
|
||||||
|
|
||||||
match lbl_ty {
|
match lbl_ty {
|
||||||
LabelType::Default | LabelType::Primary => {
|
LabelType::Default | LabelType::Primary => {
|
||||||
bounds_store.register_source_span_usage(&field.ty);
|
bounds_store.register_label_usage(&field.ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelType::Collection => {
|
LabelType::Collection => {
|
||||||
bounds_store.register_source_span_collection_usage(&field.ty);
|
bounds_store.register_label_collection_usage(&field.ty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,9 +207,10 @@ impl Labels {
|
||||||
|
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
|
.as_ref()
|
||||||
.map(|#var| #ctor(
|
.map(|#var| #ctor(
|
||||||
#display,
|
#display,
|
||||||
#var.clone(),
|
(*#var).clone(),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -229,10 +230,11 @@ impl Labels {
|
||||||
} else {
|
} else {
|
||||||
quote! { std::option::Option::None }
|
quote! { std::option::Option::None }
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
.chain({
|
.chain({
|
||||||
let display = #display;
|
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};
|
use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan};
|
||||||
let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone());
|
let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone());
|
||||||
if display.is_some() && labeled_span.label().is_none() {
|
if display.is_some() && labeled_span.label().is_none() {
|
||||||
|
|
|
||||||
|
|
@ -37,33 +37,7 @@ impl SourceCode {
|
||||||
for (i, field) in fields.iter().enumerate() {
|
for (i, field) in fields.iter().enumerate() {
|
||||||
for attr in &field.attrs {
|
for attr in &field.attrs {
|
||||||
if attr.path().is_ident("source_code") {
|
if attr.path().is_ident("source_code") {
|
||||||
let is_option = if let syn::Type::Path(syn::TypePath {
|
let is_option = TraitBoundStore::extract_option(&field.ty);
|
||||||
path: syn::Path { segments, .. },
|
|
||||||
..
|
|
||||||
}) = &field.ty
|
|
||||||
{
|
|
||||||
segments
|
|
||||||
.last()
|
|
||||||
.filter(|seg| seg.ident == "Option")
|
|
||||||
.and_then(|seg| match &seg.arguments {
|
|
||||||
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
|
|
||||||
args,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let mut iter = args.iter();
|
|
||||||
|
|
||||||
let ty = iter.next();
|
|
||||||
iter.next().xor(ty)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.and_then(|arg| match arg {
|
|
||||||
GenericArgument::Type(ty) => Some(ty),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(option_ty) = is_option {
|
if let Some(option_ty) = is_option {
|
||||||
bounds_store.register_source_code_usage(option_ty);
|
bounds_store.register_source_code_usage(option_ty);
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ use std::{
|
||||||
|
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use syn::{
|
use syn::{
|
||||||
punctuated::Punctuated, token::Plus, AngleBracketedGenericArguments, AssocType,
|
punctuated::Punctuated, token::Plus, AngleBracketedGenericArguments, AssocType, BoundLifetimes,
|
||||||
GenericArgument, GenericParam, Generics, ParenthesizedGenericArguments, Path, PathArguments,
|
GenericArgument, GenericParam, Generics, ParenthesizedGenericArguments, Path, PathArguments,
|
||||||
PathSegment, PredicateType, ReturnType, Token, Type, TypeArray, TypeGroup, TypeParamBound,
|
PathSegment, PredicateType, ReturnType, Token, TraitBound, Type, TypeArray, TypeGroup,
|
||||||
TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, TypeTuple, WhereClause, WherePredicate,
|
TypeParamBound, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, TypeTuple, WhereClause,
|
||||||
|
WherePredicate,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -19,6 +20,7 @@ pub struct RequiredTraitBound {
|
||||||
source_code: bool,
|
source_code: bool,
|
||||||
into_source_span: bool,
|
into_source_span: bool,
|
||||||
std_into_iter: bool,
|
std_into_iter: bool,
|
||||||
|
std_clone: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredTraitBound {
|
impl RequiredTraitBound {
|
||||||
|
|
@ -54,6 +56,12 @@ impl RequiredTraitBound {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.std_clone {
|
||||||
|
bounds.push(TypeParamBound::Trait(syn::parse_quote!(
|
||||||
|
::std::clone::Clone
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
if self.r#static {
|
if self.r#static {
|
||||||
bounds.push(TypeParamBound::Lifetime(syn::parse_quote!('static)))
|
bounds.push(TypeParamBound::Lifetime(syn::parse_quote!('static)))
|
||||||
}
|
}
|
||||||
|
|
@ -70,8 +78,9 @@ impl RequiredTraitBound {
|
||||||
self.source_code = true;
|
self.source_code = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_source_span_usage(&mut self) {
|
fn register_label_usage(&mut self) {
|
||||||
self.into_source_span = true;
|
self.into_source_span = true;
|
||||||
|
self.std_clone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_collection_usage(&mut self) {
|
fn register_collection_usage(&mut self) {
|
||||||
|
|
@ -88,7 +97,7 @@ impl RequiredTraitBound {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TraitBoundStore(HashMap<Type, RequiredTraitBound>);
|
pub struct TraitBoundStore(HashMap<(Option<BoundLifetimes>, Type), RequiredTraitBound>);
|
||||||
|
|
||||||
impl TraitBoundStore {
|
impl TraitBoundStore {
|
||||||
pub fn new(generics: &Generics) -> Self {
|
pub fn new(generics: &Generics) -> Self {
|
||||||
|
|
@ -114,12 +123,41 @@ impl TraitBoundStore {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(|v| (v, RequiredTraitBound::default()))
|
.map(|v| ((None, v), RequiredTraitBound::default()))
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
Self(hash_map)
|
Self(hash_map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extract_option(r#type: &Type) -> Option<&Type> {
|
||||||
|
if let syn::Type::Path(syn::TypePath {
|
||||||
|
path: syn::Path { segments, .. },
|
||||||
|
..
|
||||||
|
}) = r#type
|
||||||
|
{
|
||||||
|
segments
|
||||||
|
.last()
|
||||||
|
.filter(|seg| seg.ident == "Option")
|
||||||
|
.and_then(|seg| match &seg.arguments {
|
||||||
|
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
|
||||||
|
args, ..
|
||||||
|
}) => {
|
||||||
|
let mut iter = args.iter();
|
||||||
|
|
||||||
|
let ty = iter.next();
|
||||||
|
iter.next().xor(ty)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.and_then(|arg| match arg {
|
||||||
|
GenericArgument::Type(ty) => Some(ty),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn check_generic_usage<'ty>(&self, mut r#type: &'ty Type) -> Option<&'ty Type> {
|
fn check_generic_usage<'ty>(&self, mut r#type: &'ty Type) -> Option<&'ty Type> {
|
||||||
// in theory we could skip all this logic and just allow trivial bounds but that would add redundant trait bounds
|
// 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
|
// to the derived impl - would be another choice to make. I choose to filter as much as possible so that we don't
|
||||||
|
|
@ -161,7 +199,7 @@ impl TraitBoundStore {
|
||||||
//
|
//
|
||||||
// this is also the "bottom" check since we add the generics themselves to the map when
|
// this is also the "bottom" check since we add the generics themselves to the map when
|
||||||
// constructing self
|
// constructing self
|
||||||
if self.0.contains_key(elem) {
|
if self.0.contains_key(&(None, elem.clone())) {
|
||||||
depends_on_generic = true;
|
depends_on_generic = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -250,10 +288,6 @@ impl TraitBoundStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge_with(&self, where_clause: Option<&WhereClause>) -> Option<WhereClause> {
|
pub fn merge_with(&self, where_clause: Option<&WhereClause>) -> Option<WhereClause> {
|
||||||
if self.0.is_empty() {
|
|
||||||
return where_clause.cloned();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut where_clause = where_clause.cloned().unwrap_or_else(|| WhereClause {
|
let mut where_clause = where_clause.cloned().unwrap_or_else(|| WhereClause {
|
||||||
where_token: Token),
|
where_token: Token),
|
||||||
predicates: Punctuated::new(),
|
predicates: Punctuated::new(),
|
||||||
|
|
@ -261,15 +295,41 @@ impl TraitBoundStore {
|
||||||
|
|
||||||
where_clause
|
where_clause
|
||||||
.predicates
|
.predicates
|
||||||
.extend(self.0.iter().map(|(ty, bounds)| {
|
.extend(self.0.iter().filter_map(|(ty, bounds)| {
|
||||||
WherePredicate::Type(PredicateType {
|
let bounds = bounds.to_bounds();
|
||||||
lifetimes: None,
|
(!bounds.is_empty()).then(|| {
|
||||||
bounded_ty: ty.clone(),
|
WherePredicate::Type(PredicateType {
|
||||||
colon_token: Token),
|
lifetimes: ty.0.clone(),
|
||||||
bounds: bounds.to_bounds(),
|
bounded_ty: ty.1.clone(),
|
||||||
|
colon_token: Token),
|
||||||
|
bounds,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
where_clause
|
||||||
|
.predicates
|
||||||
|
.push(WherePredicate::Type(PredicateType {
|
||||||
|
lifetimes: None,
|
||||||
|
bounded_ty: Type::Path(TypePath {
|
||||||
|
qself: None,
|
||||||
|
path: Path {
|
||||||
|
leading_colon: None,
|
||||||
|
segments: Punctuated::from_iter(once(PathSegment {
|
||||||
|
ident: syn::Ident::new("Self", Span::mixed_site()),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
colon_token: syn::Token),
|
||||||
|
bounds: Punctuated::from_iter(once(TypeParamBound::Trait(TraitBound {
|
||||||
|
paren_token: None,
|
||||||
|
modifier: syn::TraitBoundModifier::None,
|
||||||
|
lifetimes: None,
|
||||||
|
path: syn::parse_quote!(::std::error::Error),
|
||||||
|
}))),
|
||||||
|
}));
|
||||||
|
|
||||||
Some(where_clause)
|
Some(where_clause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,7 +338,7 @@ impl TraitBoundStore {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let type_opts = self.0.entry(r#type.clone()).or_default();
|
let type_opts = self.0.entry((None, r#type.clone())).or_default();
|
||||||
type_opts.register_transparent_usage()
|
type_opts.register_transparent_usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,32 +347,45 @@ impl TraitBoundStore {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let type_opts = self.0.entry(r#type.clone()).or_default();
|
let type_opts = self.0.entry((None, r#type.clone())).or_default();
|
||||||
type_opts.register_source_code_usage()
|
type_opts.register_source_code_usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_source_span_usage(&mut self, r#type: &Type) {
|
pub fn register_label_usage(&mut self, r#type: &Type) {
|
||||||
|
let r#type = Self::extract_option(r#type).unwrap_or(r#type);
|
||||||
|
|
||||||
let Some(r#type) = self.check_generic_usage(r#type) else {
|
let Some(r#type) = self.check_generic_usage(r#type) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let type_opts = self.0.entry(r#type.clone()).or_default();
|
let type_opts = self.0.entry((None, r#type.clone())).or_default();
|
||||||
type_opts.register_source_span_usage()
|
type_opts.register_label_usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_source_span_collection_usage(&mut self, r#type: &Type) {
|
pub fn register_label_collection_usage(&mut self, r#type: &Type) {
|
||||||
let Some(ty) = self.check_generic_usage(r#type) else {
|
let Some(ty) = self.check_generic_usage(r#type) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let type_opts = self.0.entry(ty.clone()).or_default();
|
let ty: syn::Type = syn::parse_quote!(&'__miette_internal_lt #ty);
|
||||||
|
|
||||||
|
let type_opts = self
|
||||||
|
.0
|
||||||
|
.entry((
|
||||||
|
Some(syn::parse_quote!(for<'__miette_internal_lt>)),
|
||||||
|
ty.clone(),
|
||||||
|
))
|
||||||
|
.or_default();
|
||||||
type_opts.register_collection_usage();
|
type_opts.register_collection_usage();
|
||||||
|
|
||||||
let type_opts_item = self
|
let type_opts_item = self
|
||||||
.0
|
.0
|
||||||
.entry(syn::parse_quote!(<#ty as ::std::iter::IntoIterator>::Item))
|
.entry((
|
||||||
|
Some(syn::parse_quote!(for<'__miette_internal_lt>)),
|
||||||
|
syn::parse_quote!(<#ty as ::std::iter::IntoIterator>::Item),
|
||||||
|
))
|
||||||
.or_default();
|
.or_default();
|
||||||
type_opts_item.register_source_span_usage();
|
type_opts_item.register_label_usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_related_usage(&mut self, r#type: &Type) {
|
pub fn register_related_usage(&mut self, r#type: &Type) {
|
||||||
|
|
@ -320,22 +393,29 @@ impl TraitBoundStore {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let type_opts = self.0.entry(ty.clone()).or_default();
|
// this is somewhat hacky and only supports concrete types for the #[related] type
|
||||||
type_opts.register_collection_usage();
|
// 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 type_opts_item = self
|
let type_opts_item = self
|
||||||
.0
|
.0
|
||||||
.entry(syn::parse_quote!(<#ty as ::std::iter::IntoIterator>::Item))
|
.entry((
|
||||||
|
None,
|
||||||
|
syn::parse_quote!(<#ty as ::std::iter::IntoIterator>::Item),
|
||||||
|
))
|
||||||
.or_default();
|
.or_default();
|
||||||
type_opts_item.register_related_item_usage();
|
type_opts_item.register_related_item_usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn register_source_usage(&mut self, r#type: &Type) {
|
pub fn register_source_usage(&mut self, r#type: &Type) {
|
||||||
let Some(ty) = self.check_generic_usage(r#type) else {
|
let Some(ty) = self.check_generic_usage(r#type) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let type_opts = self.0.entry(ty.clone()).or_default();
|
let type_opts = self.0.entry((None, ty.clone())).or_default();
|
||||||
type_opts.register_source_usage();
|
type_opts.register_source_usage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,8 @@ fn fmt_help() {
|
||||||
#[diagnostic(code(foo::x), help("{} x {len} x {:?}", 1, "2"))]
|
#[diagnostic(code(foo::x), help("{} x {len} x {:?}", 1, "2"))]
|
||||||
Y { len: usize },
|
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"))]
|
#[diagnostic(code(foo::x), help("{} x {self:?} x {:?}", 1, "2"))]
|
||||||
Z,
|
Z,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue