#![cfg_attr(rustfmt, rustfmt::skip)] use { ::core::{ mem, slice, }, super::*, self::{ coercions::{ Coercible, }, receiver_types::{ ReceiverKind, ReceiverType, }, vtable_entry::{ VTableEntry, vtable_entries, }, } }; mod args; mod coercions; mod receiver_types; mod vtable_entry; enum Input { Trait(ItemTrait), TokenStream(TokenStream2), } impl Parse for Input { fn parse (input: ParseStream<'_>) -> Result {Ok({ let ref fork = input.fork(); fork.parse::() .map(|trait_| { ::syn::parse::discouraged::Speculative::advance_to(input, fork); Input::Trait(trait_) }) .unwrap_or_else(|_| Input::TokenStream(input.parse().unwrap())) })} } pub(in super) fn try_handle_trait ( args: &'_ mut TokenStream2, input: &'_ mut TokenStream2, ) -> Result< Option > { #[apply(let_quote!)] use ::safer_ffi::{ ඞ, ඞ::Box, dyn_traits::{ErasedTy, VirtualPtr, VirtualPtrFrom}, }; let ref coercibles = [ Coercible::Box, Coercible::Ref { mut_: false }, Coercible::Ref { mut_: true }, Coercible::Rc { thread_safe: true }, Coercible::Rc { thread_safe: false }, ]; let ref mut trait_ = match parse2(mem::take(input)).unwrap() { | Input::TokenStream(ts) => { *input = ts.into(); return Ok(None); }, | Input::Trait(it) => { *input = it.to_token_stream().into(); it }, }; let args: args::Args = parse2(mem::take(args))?; let Klone @ _ = args.clone.as_ref(); let mut ret = TokenStream2::new(); let ItemTrait { attrs: _, vis: ref pub_, unsafety: _, // FIXME auto_token: _, trait_token: _, ident: ref TraitName @ _, ref generics, colon_token: _, supertraits: _, brace_token: _, ref mut items, } = *trait_; let (_, fwd_trait_generics, trait_where_clause) = &generics.split_for_impl() ; let ref each_vtable_entry = vtable_entries(items, &mut ret)?; let each_method_def = each_vtable_entry.vmap(VTableEntry::virtual_forwarding); let each_vtable_entry_name = each_vtable_entry.vmap(VTableEntry::name); let each_vtable_entry_attrs = each_vtable_entry.vmap(VTableEntry::attrs); let (EachVTableEntryType @ _, each_vtable_entry_value_f) = each_vtable_entry .iter() .map(VTableEntry::type_and_value) .unzip::<_, _, Vec<_>, Vec<_>>() ; let VTableName @ _ = format_ident!("{}VTable", TraitName); let ref impl_Trait = format_ident!("__impl_{}", TraitName); // Original generics but for introducing the `'usability` lifetime param. let mut trait_generics_and_lt = generics.clone(); let ref lifetime = squote!( '__usability ); trait_generics_and_lt.params.insert(0, parse_quote!( #lifetime )); trait_generics_and_lt .make_where_clause() .predicates .extend_::( generics .type_params() .map(|it| &it.ident) .map(|T @ _| parse_quote!( #T : #lifetime )) ) ; let ref trait_generics_and_lt = trait_generics_and_lt; let (intro_trait_generics_and_lt, fwd_trait_generics_and_lt, _) = &trait_generics_and_lt.split_for_impl() ; let EachToBeInvariantParam @ _ = Iterator::chain( trait_generics_and_lt .lifetimes() .map(|LifetimeDef { lifetime, .. }| squote!( // We need something which *names* `lifetime`, // but which "yields" / results in a 'static CType. // So let's use the // non-generic-assoc-type-of-a-generic-trait trick for this. >::ItSelf )) , trait_generics_and_lt.type_params().map(|ty| { ty.ident.to_token_stream() }) ) ; let if_retain = args.clone.is_some().kleenable(); // Emit the vtable type definition let vtable_def = squote!( #[#ඞ::derive_ReprC] #[repr(C)] #pub_ struct #VTableName #intro_trait_generics_and_lt #trait_where_clause { release_vptr: unsafe extern "C" fn ( _: #ඞ::ptr::NonNullOwned< #ErasedTy >, ) , #(#if_retain retain_vptr: unsafe extern "C" fn ( _: #ඞ::ptr::NonNullRef< #ErasedTy >, ) -> #ඞ::ptr::NonNullOwned< #ErasedTy > , )* #( #(#each_vtable_entry_attrs)* #each_vtable_entry_name: #EachVTableEntryType, )* _invariant: ::core::marker::PhantomData< *mut (#( #EachToBeInvariantParam, )*) > , } ); let has_mutability = |mutability: bool| { each_vtable_entry.iter().any(|it| matches!( *it, VTableEntry::VirtualMethod { receiver: ReceiverType { kind: ReceiverKind::Reference { mut_ }, .. }, .. } if mut_ == mutability )) }; let (mut has_pin, mut has_non_pin_non_ref) = (None, None); each_vtable_entry .iter() .filter_map(|it| match *it { | VTableEntry::VirtualMethod { ref receiver, ref src, .. } => { Some((receiver, || src.sig.ident.clone())) }, }) .for_each(|(receiver, fname)| match receiver { | ReceiverType { pinned: true, .. } => { has_pin.get_or_insert_with(fname); }, // Tolerate `&self` refs mixed with `Pin`s: | ReceiverType { kind: ReceiverKind::Reference { mut_: false }, .. } => { /* OK */ }, | _otherwise => { has_non_pin_non_ref.get_or_insert_with(fname); }, }) ; match (&has_pin, has_non_pin_non_ref) { | (Some(pinned_span), Some(non_pin_non_ref_span)) => bail! { "`Pin<>` receivers can only be mixed with `&self` receivers" => quote!(#pinned_span #non_pin_non_ref_span) }, | _ => {}, } let has_pin = has_pin.is_some(); let (has_ref, has_mut) = (has_mutability(false), has_mutability(true)); let send_trait = &squote!( #ඞ::marker::Send ); let sync_trait = &squote!( #ඞ::marker::Sync ); for &(is_send, is_sync) in &[ (false, false), (true, true), (true, false), (false, true), ] { // given the *unconditional* presence of drop glue, the thread-safe // intent of `Sync` without `Send` is a code smell: if is_sync && is_send.not() { continue; } // with at least one `&self` method, the dual applies as well: if has_ref && is_send && is_sync.not() { continue; } let mb_send = is_send.then(|| send_trait); let mb_sync = is_sync.then(|| sync_trait); // Make `Send` and `Sync` be `#( … )*` usable (no `#( … )?`). let Send @ _ = mb_send.kleenable(); let Sync @ _ = mb_sync.kleenable(); let ref Trait @ _ = squote!( #lifetime #(+ #Send)* #(+ #Sync)* + #TraitName #fwd_trait_generics ); // trait_generics_and_lt + `impl_Trait` generic parameter. let mut all_generics = trait_generics_and_lt.clone(); let param_count = ::add( all_generics.lifetimes().count(), all_generics.type_params().count(), ); all_generics.params.insert(param_count, parse_quote!( #impl_Trait : #Trait )); let (intro_all_generics, fwd_all_generics, where_clause) = &all_generics.split_for_impl() ; let mut storage = None; let ref where_clause_with_mb_clone = if args.clone.is_some() { Some(&*storage.get_or_insert( all_generics .where_clause .as_ref() .map_or_else(|| parse_quote!(where), Clone::clone) .also(|it| it .predicates .extend_one_::( parse_quote!( #impl_Trait : #Klone ) ) ) )) } else { all_generics.where_clause.as_ref() }; let QSelf @ _ = squote!( <#impl_Trait as #TraitName #fwd_trait_generics> ); let ref EACH_VTABLE_ENTRY_VALUE @ _ = each_vtable_entry_value_f.iter().vmap(|f| f(&QSelf, &all_generics)) ; let ref e = coercions::Env { ඞ, Box, ErasedTy, Trait, impl_Trait, lifetime, intro_all_generics, intro_trait_generics_and_lt, fwd_all_generics, fwd_trait_generics_and_lt, where_clause, trait_where_clause, where_clause_with_mb_clone, Clone: args.clone.as_ref(), is_send, is_sync, has_mut, has_pin, }; let vtable_expr = |src: &Coercible| squote!( #VTableName { #{ src.release_vptr(e) } #(#if_retain #{ src.retain_vptr(e) } )* #( #(#each_vtable_entry_attrs)* #each_vtable_entry_name: #EACH_VTABLE_ENTRY_VALUE, )* _invariant: #ඞ::marker::PhantomData, } ); let constructors: TokenStream2 = coercibles .iter() .filter(|it| it.is_eligible(e)) .map(|src: &Coercible| squote!( impl #intro_all_generics #VirtualPtrFrom< #{ src.as_type(e) } > for dyn #Trait #{ if matches!(src, Coercible::Box) { where_clause_with_mb_clone } else { where_clause } } { fn into_virtual_ptr ( this: #{ src.as_type(e) }, ) -> #VirtualPtr { unsafe { #VirtualPtr::::from_raw_parts( #ඞ::mem::transmute( #{ src.into_raw(e) } ), #{ vtable_expr(src) }, ) } } } )) .collect() ; ret.extend(squote!( impl #intro_trait_generics_and_lt ::safer_ffi::dyn_traits::ReprCTrait for dyn #Trait #trait_where_clause { type VTable = #VTableName #fwd_trait_generics_and_lt; unsafe fn drop_ptr ( ptr: #ඞ::ptr::NonNullOwned<#ErasedTy>, &Self::VTable { release_vptr, .. }: &'_ Self::VTable, ) { release_vptr(ptr) } } #(#if_retain impl<#lifetime> ::safer_ffi::dyn_traits::DynClone for dyn #Trait { fn dyn_clone (this: &#VirtualPtr) -> #VirtualPtr { let (ptr, vtable) = (this.__ptr(), this.__vtable()); unsafe { let ptr = (vtable.retain_vptr)(ptr.into()); #VirtualPtr::from_raw_parts( ptr, #VTableName { ..*vtable }, ) } } } )* impl #intro_trait_generics_and_lt #TraitName #fwd_trait_generics for #VirtualPtr #trait_where_clause { #(#each_method_def)* } #constructors )); } drop(each_vtable_entry_value_f); ret = squote!( #trait_ #vtable_def #[allow(warnings, clippy::all)] const _: () = { #ret }; ); Ok(Some(ret)) } fn CType (ty: &'_ Type) -> TokenStream2 { /* replace lifetimes inside `T` with … `'static`?? */ let mut T = ty.clone(); ::syn::visit_mut::VisitMut::visit_type_mut( &mut RemapNonStaticLifetimesTo { new_lt_name: "static" }, &mut T, ); squote!( ::safer_ffi::ඞ::CLayoutOf<#T> ) }