use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::spanned::Spanned;
use crate::sassi_path;
pub fn trait_impl(args: TokenStream, input: TokenStream) -> TokenStream {
trait_impl_impl(args.into(), input.into()).into()
}
fn trait_impl_impl(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
let item = match syn::parse2::<syn::ItemImpl>(input) {
Ok(item) => item,
Err(e) => return e.to_compile_error(),
};
if !args.is_empty() {
return syn::Error::new(args.span(), "sassi::trait_impl takes no arguments")
.to_compile_error();
}
if !item.generics.params.is_empty() || item.generics.where_clause.is_some() {
return syn::Error::new(
item.generics.span(),
"#[sassi::trait_impl] currently supports concrete, non-generic impl blocks",
)
.to_compile_error();
}
let trait_path = match &item.trait_ {
Some((None, path, _for_token)) => path.clone(),
Some((Some(not_token), _path, _for_token)) => {
return syn::Error::new(
not_token.span(),
"#[sassi::trait_impl] cannot register negative impl blocks",
)
.to_compile_error();
}
None => {
return syn::Error::new(
item.impl_token.span(),
"sassi::trait_impl must be applied to `impl Trait for Type`",
)
.to_compile_error();
}
};
let sassi_path = match sassi_path() {
Ok(path) => path,
Err(e) => return e.to_compile_error(),
};
let model_ty = &item.self_ty;
let expanded = quote! {
#item
impl #sassi_path::__private::Sealed<dyn #trait_path> for #model_ty {}
impl #sassi_path::TraitImpl<dyn #trait_path> for #model_ty {}
const _: () = {
fn __sassi_collect_trait_impl(
sassi: &#sassi_path::Sassi,
) -> ::std::boxed::Box<dyn ::std::any::Any + ::std::marker::Send + ::std::marker::Sync> {
let values: ::std::vec::Vec<::std::sync::Arc<dyn #trait_path>> =
match sassi.pool::<#model_ty>() {
Some(pool) => pool
.scope(::std::vec::Vec::new())
.collect()
.into_iter()
.map(|value| value as ::std::sync::Arc<dyn #trait_path>)
.collect(),
None => ::std::vec::Vec::new(),
};
::std::boxed::Box::new(values)
}
#sassi_path::__private::inventory::submit! {
#sassi_path::__private::TraitImplEntry {
trait_type_id: ::std::any::TypeId::of::<dyn #trait_path>(),
model_type_id: ::std::any::TypeId::of::<#model_ty>(),
collect_fn: __sassi_collect_trait_impl,
}
}
};
};
expanded
}
#[cfg(test)]
mod marker_emit_tests {
use super::trait_impl_impl;
use quote::quote;
#[test]
fn emits_marker_impl_for_dyn_trait() {
let item = quote! {
impl Searchable for Vehicle {
fn cols(&self) -> &'static [&'static str] { &["title"] }
}
};
let out = trait_impl_impl(quote! {}, item).to_string();
assert!(out.contains("impl Searchable for Vehicle"));
assert!(
out.contains("TraitImpl") && out.contains("for Vehicle"),
"expected a TraitImpl<dyn Searchable> impl for Vehicle; got: {out}"
);
assert!(
out.contains("Sealed") && out.contains("for Vehicle"),
"expected a Sealed<dyn Searchable> witness impl for Vehicle; got: {out}"
);
assert!(out.contains("TraitImplEntry"));
}
}