use proc_macro2::TokenStream;
use quote::format_ident;
use syn::spanned::Spanned as _;
use super::EmitContext;
use crate::model::*;
pub fn emit_drop(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
builder,
impl_generics,
ty_generics,
where_clause,
fields,
..
} = ctx;
let field_generics1 = fields.gen_names();
let field_generics2 = fields.gen_names();
let drop_inner = emit_drop_inner(ctx);
quote::quote! {
#[automatically_derived]
impl < #impl_generics #( const #field_generics1: ::core::primitive::bool ),* > ::core::ops::Drop for #builder < #ty_generics #(#field_generics2),* > #where_clause {
#[inline]
fn drop(&mut self) {
#drop_inner
}
}
}
}
fn emit_drop_inner(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
unchecked_builder,
impl_generics,
ty_generics,
where_clause,
fields,
packed,
..
} = ctx;
let body = emit_field_drops(ctx);
if body.is_empty() {
return TokenStream::new();
}
let pack_size = 32;
let dropped_fields = fields
.pub_api()
.filter(|f| !f.leak_on_drop && if *packed { !f.unsized_tail } else { true });
let field_count = dropped_fields.clone().count();
let mut field_vars = dropped_fields.clone().map(|f| &f.drop_flag);
let mut field_var_generics = dropped_fields.map(|f| &f.gen_name);
let pack_count = usize::div_ceil(field_count, pack_size);
let packed_idents = (0..pack_count)
.map(|i| format_ident!("packed_drop_flags_{i}"))
.collect::<Vec<_>>();
let mut pack = TokenStream::new();
let mut unpack = TokenStream::new();
for pack_ident in &packed_idents {
let field_vars = field_vars.by_ref().take(pack_size);
let field_var_generics = field_var_generics.by_ref().take(pack_size);
let mask1 = (0usize..).map(|i| 1u32 << i);
let mask2 = mask1.clone();
pack.extend(quote::quote! {
0 #( | if #field_var_generics { #mask1 } else { 0 } )*,
});
unpack.extend(quote::quote! {
#( let #field_vars = (#pack_ident & #mask2) != 0; )*
});
}
quote::quote! {
#[cold]
#[inline(never)]
fn drop_inner < #impl_generics > (
this: &mut #unchecked_builder < #ty_generics >,
#( #packed_idents: ::core::primitive::u32 ),*
) #where_clause {
#unpack
#body
}
drop_inner(&mut self.inner, #pack);
}
}
fn emit_field_drops(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext { fields, packed, .. } = ctx;
fn in_place_drop(
FieldInfo {
ident,
drop_flag,
ty,
..
}: &FieldInfo,
) -> TokenStream {
quote::quote! {
if const { ::core::mem::needs_drop::<#ty>() } && #drop_flag {
unsafe {
::core::ptr::drop_in_place(
&raw mut (*::core::mem::MaybeUninit::as_mut_ptr(&mut this.inner)).#ident,
);
}
}
}
}
fn unaligned_drop(
FieldInfo {
ident,
drop_flag,
ty,
..
}: &FieldInfo,
) -> TokenStream {
quote::quote! {
if const { ::core::mem::needs_drop::<#ty>() } && #drop_flag {
#[allow(clippy::drop_non_drop)]
unsafe {
::core::mem::drop(::core::ptr::read_unaligned(
&raw mut (*::core::mem::MaybeUninit::as_mut_ptr(&mut this.inner)).#ident
));
}
}
}
}
fn expect_no_drop(FieldInfo { ident, ty, .. }: &FieldInfo) -> TokenStream {
let message = format!("packed struct unsized tail field `{ident}` cannot be dropped");
quote::quote_spanned! {ty.span()=>
const {
::core::assert!(!::core::mem::needs_drop::<#ty>(), #message);
}
}
}
let mut output = TokenStream::new();
for field in fields.pub_api() {
if !field.leak_on_drop {
output.extend(match (packed, field.unsized_tail) {
(true, false) => unaligned_drop(field),
(true, true) => expect_no_drop(field),
(false, _) => in_place_drop(field),
});
}
}
output
}