use once_cell::unsync::Lazy;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use std::{borrow::Borrow, iter};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
token,
Lifetime,
Path,
PathSegment,
TraitBound,
TypeParamBound,
};
#[derive(Clone, PartialEq, Eq)]
pub struct MarkerTrait {
pub unsafety: Option<token::Unsafe>,
pub path: Path,
}
impl MarkerTrait {
pub fn as_impl_for(&self, implementor: &impl ToTokens) -> TokenStream {
let marker_unsafety = self.unsafety.as_ref();
let marker_path = &self.path;
quote! {
#marker_unsafety impl #marker_path for #implementor {}
}
}
}
impl Parse for MarkerTrait {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let unsafety = if input.peek(token::Unsafe) {
Some(input.parse()?)
} else {
None
};
Ok(Self {
unsafety,
path: input.parse()?,
})
}
}
pub fn supertraits_to_markers_and_lifetimes(
supertraits: Punctuated<TypeParamBound, token::Add>,
mut marker_filter: impl FnMut(TraitBound) -> Option<(TraitBound, bool)>,
) -> (Vec<MarkerTrait>, Vec<Lifetime>) {
let mut markers = Vec::with_capacity(supertraits.len());
let mut lifetimes = Vec::with_capacity(supertraits.len());
for supertrait in supertraits {
match supertrait {
TypeParamBound::Trait(trait_bound) => {
if let Some((bound, is_unsafe)) = marker_filter(trait_bound) {
markers.push(MarkerTrait {
unsafety: if is_unsafe {
Some(token::Unsafe {
span: Span::call_site(),
})
} else {
None
},
path: bound.path,
})
}
}
TypeParamBound::Lifetime(lifetime) => lifetimes.push(lifetime),
}
}
(markers, lifetimes)
}
macro_rules! make_path {
($segment:expr) => {
Path {
leading_colon: None,
segments: iter::once($segment).collect(),
}
};
[$first:expr, $($rest:expr),+] => {
Path {
leading_colon: Some(Default::default()),
segments: iter::once($first)
$(.chain(iter::once($rest)))*
.collect(),
}
};
}
fn mkseg(string: &str) -> PathSegment {
PathSegment::from(Ident::new(string, Span::call_site()))
}
pub fn default_marker_filter(bound: TraitBound) -> Option<(TraitBound, bool)> {
LOOKUP_TABLE.with(|lookup_table| {
for (short_name, full_path, is_unsafe) in lookup_table.borrow().iter().cloned() {
if bound.path == make_path!(mkseg(short_name)) || bound.path == full_path {
return Some((bound, is_unsafe));
}
}
None
})
}
thread_local! {
static LOOKUP_TABLE: Lazy<[(&'static str, Path, bool); 4]> = Lazy::new(|| {
[
(
"Send",
make_path![mkseg("core"), mkseg("marker"), mkseg("Send")],
true,
),
(
"Sync",
make_path![mkseg("core"), mkseg("marker"), mkseg("Sync")],
true,
),
(
"UnwindSafe",
make_path![mkseg("std"), mkseg("panic"), mkseg("UnwindSafe")],
false,
),
(
"RefUnwindSafe",
make_path![mkseg("std"), mkseg("panic"), mkseg("RefUnwindSafe")],
false,
),
]
});
}