Simplify the trait bound store and just register where clauses directly

Signed-off-by: Justus Flügel <justusfluegel@gmail.com>
This commit is contained in:
Justus Flügel 2025-03-10 21:36:11 +01:00 committed by Justus Fluegel
parent a23114fffe
commit ff32d81cb0
No known key found for this signature in database
GPG Key ID: DD4B1903FEACCC4D
8 changed files with 200 additions and 360 deletions

View File

@ -11,7 +11,7 @@ use crate::label::Labels;
use crate::related::Related; use crate::related::Related;
use crate::severity::Severity; use crate::severity::Severity;
use crate::source_code::SourceCode; use crate::source_code::SourceCode;
use crate::trait_bounds::TraitBoundStore; use crate::trait_bounds::TypeParamBoundStore;
use crate::url::Url; use crate::url::Url;
pub enum Diagnostic { pub enum Diagnostic {
@ -20,13 +20,13 @@ pub enum Diagnostic {
ident: syn::Ident, ident: syn::Ident,
fields: syn::Fields, fields: syn::Fields,
args: DiagnosticDefArgs, args: DiagnosticDefArgs,
bound_store: TraitBoundStore, bound_store: TypeParamBoundStore,
}, },
Enum { Enum {
ident: syn::Ident, ident: syn::Ident,
generics: syn::Generics, generics: syn::Generics,
variants: Vec<DiagnosticDef>, variants: Vec<DiagnosticDef>,
bound_store: TraitBoundStore, bound_store: TypeParamBoundStore,
}, },
} }
@ -76,7 +76,7 @@ pub struct DiagnosticConcreteArgs {
impl DiagnosticConcreteArgs { impl DiagnosticConcreteArgs {
fn for_fields( fn for_fields(
fields: &syn::Fields, fields: &syn::Fields,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> Result<Self, syn::Error> { ) -> Result<Self, syn::Error> {
let labels = Labels::from_fields(fields, bounds_store)?; let labels = Labels::from_fields(fields, bounds_store)?;
let source_code = SourceCode::from_fields(fields, bounds_store)?; let source_code = SourceCode::from_fields(fields, bounds_store)?;
@ -162,7 +162,7 @@ impl DiagnosticDefArgs {
_ident: &syn::Ident, _ident: &syn::Ident,
fields: &syn::Fields, fields: &syn::Fields,
attrs: &[&syn::Attribute], attrs: &[&syn::Attribute],
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
allow_transparent: bool, allow_transparent: bool,
) -> syn::Result<Self> { ) -> syn::Result<Self> {
let mut errors = Vec::new(); let mut errors = Vec::new();
@ -233,7 +233,7 @@ impl Diagnostic {
.collect::<Vec<&syn::Attribute>>(); .collect::<Vec<&syn::Attribute>>();
Ok(match input.data { Ok(match input.data {
syn::Data::Struct(data_struct) => { syn::Data::Struct(data_struct) => {
let mut bounds_store = TraitBoundStore::new(&input.generics); let mut bounds_store = TypeParamBoundStore::new(&input.generics);
let args = DiagnosticDefArgs::parse( let args = DiagnosticDefArgs::parse(
&input.ident, &input.ident,
@ -253,7 +253,7 @@ impl Diagnostic {
} }
syn::Data::Enum(syn::DataEnum { variants, .. }) => { syn::Data::Enum(syn::DataEnum { variants, .. }) => {
let mut vars = Vec::new(); let mut vars = Vec::new();
let mut bound_store = TraitBoundStore::new(&input.generics); let mut bound_store = TypeParamBoundStore::new(&input.generics);
for var in variants { for var in variants {
let mut variant_attrs = input_attrs.clone(); let mut variant_attrs = input_attrs.clone();
variant_attrs variant_attrs
@ -297,7 +297,7 @@ impl Diagnostic {
bound_store, 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.merge_with(where_clause); let where_clause = bound_store.add_to_where_clause(where_clause);
match args { match args {
DiagnosticDefArgs::Transparent(forward) => { DiagnosticDefArgs::Transparent(forward) => {
@ -397,7 +397,7 @@ impl Diagnostic {
bound_store, 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.merge_with(where_clause); let where_clause = bound_store.add_to_where_clause(where_clause);
let code_body = Code::gen_enum(variants); let code_body = Code::gen_enum(variants);
let help_body = Help::gen_enum(variants); let help_body = Help::gen_enum(variants);

View File

@ -3,7 +3,7 @@ use quote::quote;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use crate::forward::WhichFn; use crate::forward::WhichFn;
use crate::trait_bounds::TraitBoundStore; use crate::trait_bounds::TypeParamBoundStore;
use crate::{ use crate::{
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef}, diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
utils::{display_pat_members, gen_all_variants_with}, utils::{display_pat_members, gen_all_variants_with},
@ -14,7 +14,7 @@ pub struct DiagnosticSource(syn::Member);
impl DiagnosticSource { impl DiagnosticSource {
pub(crate) fn from_fields( pub(crate) fn from_fields(
fields: &syn::Fields, fields: &syn::Fields,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> syn::Result<Option<Self>> { ) -> syn::Result<Option<Self>> {
match fields { match fields {
syn::Fields::Named(named) => { syn::Fields::Named(named) => {
@ -29,7 +29,7 @@ impl DiagnosticSource {
fn from_fields_vec( fn from_fields_vec(
fields: Vec<&syn::Field>, fields: Vec<&syn::Field>,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> syn::Result<Option<Self>> { ) -> syn::Result<Option<Self>> {
for (i, field) in fields.iter().enumerate() { for (i, field) in fields.iter().enumerate() {
for attr in &field.attrs { for attr in &field.attrs {
@ -43,7 +43,10 @@ impl DiagnosticSource {
}) })
}; };
bounds_store.register_source_usage(&field.ty); let ty = &field.ty;
bounds_store.add_where_predicate(
syn::parse_quote!(#ty: ::miette::Diagnostic + 'static),
);
return Ok(Some(DiagnosticSource(diagnostic_source))); return Ok(Some(DiagnosticSource(diagnostic_source)));
} }

View File

@ -6,7 +6,7 @@ use syn::{
spanned::Spanned, spanned::Spanned,
}; };
use crate::trait_bounds::TraitBoundStore; use crate::trait_bounds::TypeParamBoundStore;
pub enum Forward { pub enum Forward {
Unnamed(usize), Unnamed(usize),
@ -98,7 +98,7 @@ impl WhichFn {
impl Forward { impl Forward {
pub fn for_transparent_field( pub fn for_transparent_field(
fields: &syn::Fields, fields: &syn::Fields,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> syn::Result<Self> { ) -> syn::Result<Self> {
let make_err = || { let make_err = || {
syn::Error::new( syn::Error::new(
@ -118,7 +118,9 @@ impl Forward {
.clone() .clone()
.unwrap_or_else(|| format_ident!("unnamed")); .unwrap_or_else(|| format_ident!("unnamed"));
bounds_store.register_transparent_usage(&field.ty); let ty = &field.ty;
bounds_store
.add_where_predicate(syn::parse_quote! {#ty: ::miette::Diagnostic + 'static});
Ok(Self::Named(field_name)) Ok(Self::Named(field_name))
} }
syn::Fields::Unnamed(unnamed) => { syn::Fields::Unnamed(unnamed) => {
@ -128,7 +130,9 @@ impl Forward {
return Err(make_err()); return Err(make_err());
} }
bounds_store.register_transparent_usage(&field.ty); let ty = &field.ty;
bounds_store
.add_where_predicate(syn::parse_quote! {#ty: ::miette::Diagnostic + 'static});
Ok(Self::Unnamed(0)) Ok(Self::Unnamed(0))
} }
_ => Err(syn::Error::new( _ => Err(syn::Error::new(

View File

@ -4,15 +4,15 @@ use syn::{
parenthesized, parenthesized,
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
spanned::Spanned, spanned::Spanned,
Token, Lifetime, Token,
}; };
use crate::{ use crate::{
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef}, diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
fmt::{self, Display}, fmt::{self, Display},
forward::WhichFn, forward::WhichFn,
trait_bounds::TraitBoundStore, trait_bounds::TypeParamBoundStore,
utils::{display_pat_members, gen_all_variants_with}, utils::{display_pat_members, extract_option, gen_all_variants_with},
}; };
pub struct Labels(Vec<Label>); pub struct Labels(Vec<Label>);
@ -110,7 +110,7 @@ impl Parse for LabelAttr {
impl Labels { impl Labels {
pub fn from_fields( pub fn from_fields(
fields: &syn::Fields, fields: &syn::Fields,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> syn::Result<Option<Self>> { ) -> syn::Result<Option<Self>> {
match fields { match fields {
syn::Fields::Named(named) => { syn::Fields::Named(named) => {
@ -125,7 +125,7 @@ impl Labels {
fn from_fields_vec( fn from_fields_vec(
fields: Vec<&syn::Field>, fields: Vec<&syn::Field>,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> syn::Result<Option<Self>> { ) -> syn::Result<Option<Self>> {
let mut labels = Vec::new(); let mut labels = Vec::new();
for (i, field) in fields.iter().enumerate() { for (i, field) in fields.iter().enumerate() {
@ -156,11 +156,31 @@ impl Labels {
match lbl_ty { match lbl_ty {
LabelType::Default | LabelType::Primary => { LabelType::Default | LabelType::Primary => {
bounds_store.register_label_usage(&field.ty); 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 => { LabelType::Collection => {
bounds_store.register_label_collection_usage(&field.ty); 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>
});
} }
} }

View File

@ -5,7 +5,7 @@ use syn::spanned::Spanned;
use crate::{ use crate::{
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef}, diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
forward::WhichFn, forward::WhichFn,
trait_bounds::TraitBoundStore, trait_bounds::TypeParamBoundStore,
utils::{display_pat_members, gen_all_variants_with}, utils::{display_pat_members, gen_all_variants_with},
}; };
@ -14,7 +14,7 @@ pub struct Related(syn::Member);
impl Related { impl Related {
pub(crate) fn from_fields( pub(crate) fn from_fields(
fields: &syn::Fields, fields: &syn::Fields,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> syn::Result<Option<Self>> { ) -> syn::Result<Option<Self>> {
match fields { match fields {
syn::Fields::Named(named) => { syn::Fields::Named(named) => {
@ -29,7 +29,7 @@ impl Related {
fn from_fields_vec( fn from_fields_vec(
fields: Vec<&syn::Field>, fields: Vec<&syn::Field>,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> syn::Result<Option<Self>> { ) -> syn::Result<Option<Self>> {
for (i, field) in fields.iter().enumerate() { for (i, field) in fields.iter().enumerate() {
for attr in &field.attrs { for attr in &field.attrs {
@ -42,7 +42,17 @@ impl Related {
span: field.span(), span: field.span(),
}) })
}; };
bounds_store.register_related_usage(&field.ty); // 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))); return Ok(Some(Related(related)));
} }
} }

View File

@ -1,12 +1,12 @@
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{spanned::Spanned, AngleBracketedGenericArguments, GenericArgument, PathArguments}; use syn::spanned::Spanned;
use crate::{ use crate::{
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef}, diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
forward::WhichFn, forward::WhichFn,
trait_bounds::TraitBoundStore, trait_bounds::TypeParamBoundStore,
utils::{display_pat_members, gen_all_variants_with}, utils::{display_pat_members, extract_option, gen_all_variants_with},
}; };
pub struct SourceCode { pub struct SourceCode {
@ -17,7 +17,7 @@ pub struct SourceCode {
impl SourceCode { impl SourceCode {
pub fn from_fields( pub fn from_fields(
fields: &syn::Fields, fields: &syn::Fields,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> syn::Result<Option<Self>> { ) -> syn::Result<Option<Self>> {
match fields { match fields {
syn::Fields::Named(named) => { syn::Fields::Named(named) => {
@ -32,18 +32,16 @@ impl SourceCode {
fn from_fields_vec( fn from_fields_vec(
fields: Vec<&syn::Field>, fields: Vec<&syn::Field>,
bounds_store: &mut TraitBoundStore, bounds_store: &mut TypeParamBoundStore,
) -> syn::Result<Option<Self>> { ) -> syn::Result<Option<Self>> {
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 = TraitBoundStore::extract_option(&field.ty); let is_option = extract_option(&field.ty);
if let Some(option_ty) = is_option { let code_ty = is_option.unwrap_or(&field.ty);
bounds_store.register_source_code_usage(option_ty); bounds_store
} else { .add_where_predicate(syn::parse_quote!(#code_ty: ::miette::SourceCode));
bounds_store.register_source_code_usage(&field.ty);
}
let source_code = if let Some(ident) = field.ident.clone() { let source_code = if let Some(ident) = field.ident.clone() {
syn::Member::Named(ident) syn::Member::Named(ident)

View File

@ -1,176 +1,51 @@
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, HashSet, VecDeque},
iter::{once, FromIterator}, iter::once,
}; };
use proc_macro2::Span; use proc_macro2::Span;
use syn::{ use syn::{
punctuated::Punctuated, token::Plus, AngleBracketedGenericArguments, AssocType, BoundLifetimes, punctuated::Punctuated, AngleBracketedGenericArguments, AssocType, BoundLifetimes,
GenericArgument, GenericParam, Generics, ParenthesizedGenericArguments, Path, PathArguments, GenericArgument, GenericParam, Generics, ParenthesizedGenericArguments, PathArguments,
PathSegment, PredicateType, ReturnType, Token, TraitBound, Type, TypeArray, TypeGroup, PredicateType, ReturnType, Token, Type, TypeArray, TypeGroup, TypeParamBound, TypeParen,
TypeParamBound, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, TypeTuple, WhereClause, TypePath, TypePtr, TypeReference, TypeSlice, TypeTuple, WhereClause, WherePredicate,
WherePredicate,
}; };
#[derive(Default)] // Potential improvement, although idk if this actually ends up
pub struct RequiredTraitBound { // mattering is to switch this to something like FxHashMap like the rustc compiler uses internally
r#static: bool, pub struct TypeParamBoundStore(HashMap<(Option<BoundLifetimes>, Type), HashSet<TypeParamBound>>);
std_error: bool,
miette_diagnostic: bool,
source_code: bool,
into_source_span: bool,
std_into_iter: bool,
std_deref: bool,
std_to_owned: bool,
}
impl RequiredTraitBound { impl TypeParamBoundStore {
fn to_bounds(&self) -> Punctuated<TypeParamBound, Plus> { /// Creates a new TraitBoundStore, filling it with some generics which are used to heuristically remove trivial bounds.
let mut bounds = Punctuated::new(); ///
if self.std_error && !self.miette_diagnostic { /// 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.
bounds.push(TypeParamBound::Trait(syn::parse_quote!(
::std::error::Error
)));
}
if self.miette_diagnostic {
bounds.push(TypeParamBound::Trait(syn::parse_quote!(
::miette::Diagnostic
)))
}
if self.source_code {
bounds.push(TypeParamBound::Trait(syn::parse_quote!(
::miette::SourceCode
)))
}
if self.into_source_span {
bounds.push(TypeParamBound::Trait(syn::parse_quote!(
::std::convert::Into<::miette::SourceSpan>
)))
}
if self.std_into_iter {
bounds.push(TypeParamBound::Trait(syn::parse_quote!(
::std::iter::IntoIterator
)))
}
if self.std_deref {
bounds.push(TypeParamBound::Trait(syn::parse_quote!(::std::ops::Deref)))
}
if self.std_to_owned {
bounds.push(TypeParamBound::Trait(syn::parse_quote!(
::std::borrow::ToOwned
)))
}
if self.r#static {
bounds.push(TypeParamBound::Lifetime(syn::parse_quote!('static)))
}
bounds
}
fn register_transparent_usage(&mut self) {
self.r#static = true;
self.miette_diagnostic = true;
}
fn register_source_code_usage(&mut self) {
self.source_code = true;
}
fn register_label_usage(&mut self) {
self.into_source_span = true;
}
fn register_collection_usage(&mut self) {
self.std_into_iter = true;
}
fn register_related_item_usage(&mut self) {
self.miette_diagnostic = true;
self.r#static = true;
}
fn register_source_usage(&mut self) {
self.miette_diagnostic = true;
self.r#static = true;
}
fn register_deref_usage(&mut self) {
self.std_deref = true;
}
fn register_to_owned_usage(&mut self) {
self.std_to_owned = true;
}
}
pub struct TraitBoundStore(HashMap<(Option<BoundLifetimes>, Type), RequiredTraitBound>);
impl TraitBoundStore {
pub fn new(generics: &Generics) -> Self { pub fn new(generics: &Generics) -> Self {
let hash_map = generics let hash_map = generics
.params .params
.iter() .iter()
.filter_map(|param| { .filter_map(|param| match param {
if let GenericParam::Type(ty) = param { GenericParam::Type(ty) => Some(ty),
Some(ty) _ => None,
} else {
None
}
}) })
.map(|param| { .map(|param| {
Type::Path(TypePath { let ident = &param.ident;
qself: None, Type::Path(syn::parse_quote!(#ident))
path: Path {
leading_colon: None,
segments: Punctuated::from_iter(once(PathSegment {
ident: param.ident.clone(),
arguments: PathArguments::None,
})),
},
})
}) })
.map(|v| ((None, v), RequiredTraitBound::default())) .map(|ty| ((None, ty), Default::default()))
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
Self(hash_map) Self(hash_map)
} }
pub fn extract_option(r#type: &Type) -> Option<&Type> { /// Checks heuristically if `type` is using any generic type inside it.
if let syn::Type::Path(syn::TypePath { ///
path: syn::Path { segments, .. }, /// This is guaranteed to never false-negative but might
.. /// false-positive if checking exhaustively would be expensive or
}) = r#type /// an unexpected case is encountered which this can't handle.
{ ///
segments /// # Returns
.last() /// Option with a simplified type if determined to be dependant, none otherwise
.filter(|seg| seg.ident == "Option") fn generic_usage_heuristics(&self, mut r#type: Type) -> Option<Type> {
.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> {
// 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
// introduce unneccessary bounds. // introduce unneccessary bounds.
@ -178,8 +53,8 @@ impl TraitBoundStore {
// this reduces the type down as much as possible to remove unneeded groups. // this reduces the type down as much as possible to remove unneeded groups.
let original_type = loop { let original_type = loop {
match r#type { match r#type {
Type::Paren(TypeParen { elem, .. }) => r#type = &**elem, Type::Paren(TypeParen { elem, .. }) => r#type = *elem,
Type::Group(TypeGroup { elem, .. }) => r#type = &**elem, Type::Group(TypeGroup { elem, .. }) => r#type = *elem,
x => break x, x => break x,
} }
}; };
@ -193,7 +68,7 @@ impl TraitBoundStore {
let max_depth = 8; let max_depth = 8;
let mut to_check_queue: VecDeque<(&Type, usize)> = VecDeque::new(); let mut to_check_queue: VecDeque<(&Type, usize)> = VecDeque::new();
to_check_queue.push_back((original_type, 0)); to_check_queue.push_back((&original_type, 0));
while !depends_on_generic { while !depends_on_generic {
// this needs to be like this cuz if-let-chains aren't supported yet // this needs to be like this cuz if-let-chains aren't supported yet
@ -299,173 +174,70 @@ impl TraitBoundStore {
depends_on_generic.then_some(original_type) depends_on_generic.then_some(original_type)
} }
pub fn merge_with(&self, where_clause: Option<&WhereClause>) -> Option<WhereClause> { pub fn add_predicate(
let mut where_clause = where_clause.cloned().unwrap_or_else(|| WhereClause { &mut self,
where_token: Token![where](Span::mixed_site()), PredicateType {
predicates: Punctuated::new(), lifetimes,
}); bounded_ty,
colon_token: _,
bounds,
}: PredicateType,
) {
let Some(bounded_ty) = self.generic_usage_heuristics(bounded_ty) else {
return;
};
where_clause self.0
.predicates .entry((lifetimes, bounded_ty))
.extend(self.0.iter().filter_map(|(ty, bounds)| { .or_default()
let bounds = bounds.to_bounds(); .extend(bounds);
(!bounds.is_empty()).then(|| { }
WherePredicate::Type(PredicateType {
lifetimes: ty.0.clone(), // Since syn for some reason doesn't implement `Parse` for `PredicateType`
bounded_ty: ty.1.clone(), // this method is meant for ease of use with `syn::parse_quote!`.
colon_token: Token![:](Span::mixed_site()), pub fn add_where_predicate(&mut self, predicate: WherePredicate) {
bounds, 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![:](Span::mixed_site()),
bounds: bounds.into_iter().collect(),
}) })
})); })
.peekable();
where_clause // de-duplicate elements newly added and within existing where clause
.predicates let predicates = predicates
.push(WherePredicate::Type(PredicateType { .chain(
lifetimes: None, where_clause
bounded_ty: Type::Path(TypePath { .into_iter()
qself: None, .flat_map(|where_clause| where_clause.predicates.clone()),
path: Path { )
leading_colon: None, .chain(once(syn::parse_quote!(Self: ::std::error::Error)))
segments: Punctuated::from_iter(once(PathSegment { .collect::<HashSet<_>>();
ident: syn::Ident::new("Self", Span::mixed_site()),
arguments: PathArguments::None,
})),
},
}),
colon_token: syn::Token![:](Span::mixed_site()),
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(WhereClause {
} where_token: Token![where](Span::mixed_site()),
predicates: predicates.into_iter().collect(),
pub fn register_transparent_usage(&mut self, r#type: &Type) { })
let Some(r#type) = self.check_generic_usage(r#type) else {
return;
};
let type_opts = self.0.entry((None, r#type.clone())).or_default();
type_opts.register_transparent_usage()
}
pub fn register_source_code_usage(&mut self, r#type: &Type) {
let Some(r#type) = self.check_generic_usage(r#type) else {
return;
};
let type_opts = self.0.entry((None, r#type.clone())).or_default();
type_opts.register_source_code_usage()
}
pub fn register_label_usage(&mut self, r#type: &Type) {
let r#type = Self::extract_option(r#type).unwrap_or(r#type);
let Some(ty) = self.check_generic_usage(r#type) else {
return;
};
let type_opts = self.0.entry((None, ty.clone())).or_default();
type_opts.register_to_owned_usage();
let type_opts_to_owned = self
.0
.entry((
None,
syn::parse_quote!(<#ty as ::std::borrow::ToOwned>::Owned),
))
.or_default();
type_opts_to_owned.register_label_usage();
}
pub fn register_label_collection_usage(&mut self, r#type: &Type) {
let Some(ty) = self.check_generic_usage(r#type) else {
return;
};
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();
let type_opts_item = self
.0
.entry((
Some(syn::parse_quote!(for<'__miette_internal_lt>)),
syn::parse_quote!(<#ty as ::std::iter::IntoIterator>::Item),
))
.or_default();
type_opts_item.register_deref_usage();
let type_opts_deref_item = self
.0
.entry((
Some(syn::parse_quote! {for<'__miette_internal_lt>}),
syn::parse_quote! {
<<#ty as ::std::iter::IntoIterator>::Item as ::std::ops::Deref>::Target
},
))
.or_default();
type_opts_deref_item.register_to_owned_usage();
let type_opts_deref_to_owned_item = self
.0
.entry((
Some(syn::parse_quote! {for<'__miette_internal_lt>}),
syn::parse_quote! {
<
<
<#ty as ::std::iter::IntoIterator>::Item
as ::std::ops::Deref
>::Target
as ::std::borrow::ToOwned
>::Owned
},
))
.or_default();
type_opts_deref_to_owned_item.register_label_usage();
}
pub fn register_related_usage(&mut self, r#type: &Type) {
let Some(ty) = self.check_generic_usage(r#type) else {
return;
};
// 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 type_opts_item = self
.0
.entry((
None,
syn::parse_quote!(<#ty as ::std::iter::IntoIterator>::Item),
))
.or_default();
type_opts_item.register_related_item_usage();
}
pub fn register_source_usage(&mut self, r#type: &Type) {
let Some(ty) = self.check_generic_usage(r#type) else {
return;
};
let type_opts = self.0.entry((None, ty.clone())).or_default();
type_opts.register_source_usage();
} }
} }

View File

@ -1,6 +1,6 @@
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::spanned::Spanned; use syn::{spanned::Spanned, AngleBracketedGenericArguments, GenericArgument, PathArguments, Type};
use crate::{ use crate::{
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef}, diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
@ -104,3 +104,36 @@ impl Display {
(fmt, args) (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)
}