use super::*;
#[derive(Debug,FromDeriveInput)]
#[darling(attributes(partial_borrow))]
struct Instructions {
vis: syn::Visibility,
ident: syn::Ident,
generics: syn::Generics,
data: darling::ast::Data<(),syn::Field>,
#[darling(default,rename="Debug")] debug: bool,
#[darling(default)] partial: Option<syn::Ident>,
#[darling(default)] module: Option<syn::Ident>,
#[darling(default)] suffix: Option<String>,
#[darling(default)] imported_for_test_unsafe: bool,
}
#[allow(non_snake_case)]
struct MutOrConst {
Deref_ : syn::Ident,
IsRef_ : syn::Ident,
as_ref_ : syn::Ident,
AsRef_ : syn::Ident,
const_ : TokenStream2,
mut_sfx : &'static str,
mut_ : Option<TokenStream2>,
m : bool,
}
macro_rules! mut_or_const_bind { { $m:expr, $($local:ident)* ;
$($x:tt)*
} => {
{
#[allow(non_snake_case)]
#[allow(unused_variables)]
let MutOrConst { $( $local, )* .. } = MutOrConst {
Deref_ : if $m { fi!("DerefMut" )} else { fi!("Deref") },
mut_sfx : if $m { "_mut" } else { "" },
IsRef_ : if $m { fi!("IsMut") } else { fi!("IsRef") },
as_ref_ : if $m { fi!("as_mut") } else { fi!("as_ref") },
AsRef_ : if $m { fi!("AsMut") } else { fi!("AsRef") },
const_ : if $m { quote!(mut) } else { quote!(const) },
mut_ : if $m { Some(quote!(mut)) } else { None },
m : $m,
};
$($x)*
}
} }
macro_rules! mut_or_const { { $($local:ident)* ;
$($x:tt)*
} => {
for m in [false,true] {
mut_or_const_bind!{ m, $($local)* ; $($x)* }
}
} }
#[proc_macro_error(allow_not_macro)]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let input: Instructions = match FromDeriveInput::from_derive_input(&input) {
Ok(y) => y,
Err(e) => return proc_macro::TokenStream::from(e.write_errors()),
};
let vis = &input.vis;
let name = &input.ident;
let us = ourselves_ident();
let imp = if input.imported_for_test_unsafe { quote!{} }
else { quote!{ #us::imports:: } };
let sfx = input.suffix.as_deref().unwrap_or("__");
#[allow(non_snake_case)] let vP = format_ident!("P{}", sfx);
#[allow(non_snake_case)] let vS = format_ident!("S{}", sfx);
#[allow(non_snake_case)] let vT = format_ident!("T{}", sfx);
#[allow(non_snake_case)] let vN = format_ident!("N{}", sfx);
let vlft = syn::Lifetime {
ident: format_ident!("r{}", sfx),
apostrophe: Span2::call_site(),
};
let fields = match input.data {
darling::ast::Data::Struct(s)
if s.style == darling::ast::Style::Struct
=> s.fields,
_ => abort_call_site!("PartialBorrow can only be derived for structs"),
};
let name_some = |input_field: &Option<syn::Ident>, ending| {
if let Some(spec) = input_field { spec.clone() }
else { format_ident!("{}{}{}", &name, sfx, ending) }
};
let name_parts = name_some(&input.partial, "Partial");
let name_mod = name_some(&input.module, "");
let mut in_lifetimes = vec![]; let mut in_lifetimes_act = vec![];
let mut in_generics = vec![]; let mut in_generics_act = vec![];
for g in input.generics.params {
use syn::GenericParam as GP;
match g {
GP::Lifetime(t) => {
let p = &t.lifetime;
let p = quote_spanned!{p.span()=> #p };
in_lifetimes .push(p.clone());
in_lifetimes_act.push(p);
},
GP::Type(t) => {
let p = &t.ident;
let p = quote_spanned!{p.span()=> #p };
in_generics .push(p.clone());
in_generics_act.push(p);
},
GP::Const(t) => {
let p = &t.ident;
let ty = &t.ty;
in_generics .push(quote_spanned!{p.span()=> const #p: #ty });
in_generics_act.push(quote_spanned!{p.span()=> #p});
},
}
}
let ilts = quote!{ #(#in_lifetimes ,)* };
let ilta = quote!{ #(#in_lifetimes_act,)* };
let igens = quote!{ #(#in_generics ,)* };
let igena = quote!{ #(#in_generics_act ,)* };
let whole = quote!{ #name< #(#in_lifetimes_act,)* #(#in_generics_act,)* > };
let iwhere = &input.generics.where_clause;
let ipreds = iwhere.as_ref().map(|w| &w.predicates);
use format_ident as fi;
let mut impls = vec![];
let mut parts_fields = vec![];
let mut mod_fields = vec![];
let mut debug_fields = vec![];
let mut field_types = vec![];
let mut p_gens = vec![];
let mut r_gens = vec![];
let mut s_gens = vec![];
for f in fields.iter() {
let fty = &f.ty;
let fname = f.ident.as_ref().unwrap();
let fname_str = fname.to_string();
let permit_generic = format_ident!("P{}{}", sfx, fname);
let fref = format_ident!("F_{}", fname);
let fref_generics = quote!{ <#ilts #vP, #vT, #igens> };
mod_fields.push(quote!{
#[repr(C)]
pub struct #fref<#vP,#vT,#vS> {
p: #vP,
t: PhantomData<#vT>,
s: PhantomData< *const #vS >,
}
});
mut_or_const!{
Deref_ IsRef_ as_ref_ AsRef_ const_ mut_sfx mut_;
let target_= if mut_.is_some() { None }
else { Some(quote!{ type Target = #vT; }) }.into_iter();
let mc_deref_f = format_ident!("deref{}", mut_sfx);
impls.push(quote!{
impl #fref_generics #imp #Deref_ for #name_mod::#fref<#vP,#vT,#whole>
where #vP: #imp #IsRef_, #ipreds
{
#(#target_)*
fn #mc_deref_f(& #mut_ self) -> &#mut_ #vT where #vT: #imp Sized {
unsafe {
let p: *#const_ Self = self;
let p: *#const_ #whole = p as usize as _;
let offset = #imp offset_of!(#whole, #fname);
let p: * #const_ u8 = p as _;
let p = p.add(offset);
let p: * #const_ #vT = p as _;
let p: & #mut_ #vT = p.#as_ref_().unwrap();
p
}
}
}
});
}
if input.debug {
debug_fields.push(quote!{
#imp fmt::DebugStruct::field(&mut fields, #fname_str, &self.#fname);
});
impls.push(quote!{
impl #fref_generics #imp Debug
for #name_mod::#fref<#vP,#vT,#whole>
where #vT: #imp Debug,
#vP: #imp IsRefOrNot,
#ipreds
{
fn fmt<#vlft>(&#vlft self, f: &mut #imp Formatter)
-> #imp fmt::Result
{
if let #imp Some(#imp Const) = <#vP as #imp IsRefOrNot>::REF {
let downgraded = unsafe {
#imp transmute::<
&#vlft #name_mod::#fref< #vP, #vT, #whole>,
&#vlft #name_mod::#fref<#imp Const, #vT, #whole>,
>(self)
};
#imp Debug::fmt(&**downgraded, f)
} else {
#imp Formatter::write_str(f, "_")
}
}
}
});
}
let fref = quote!{
#name_mod::#fref<
#permit_generic,
#fty,
#whole
>
};
field_types.push(fref.clone());
parts_fields.push(syn::Field {
attrs: default(),
vis: f.vis.clone(),
ident: f.ident.clone(),
colon_token: f.colon_token.clone(),
ty: {
let mut ty = default();
f.ty.to_tokens(&mut ty);
syn::parse2(fref).expect("internal error in PartialBorrow derive")
},
});
p_gens.push(permit_generic);
r_gens.push(format_ident!("R{}{}", sfx, fname));
s_gens.push(format_ident!("S{}{}", sfx, fname));
}
let p_gens = p_gens;
let r_gens = r_gens;
let s_gens = s_gens;
let parts_p = quote!{ #name_parts<#ilta #(#p_gens,)* #igena> };
let parts_r = quote!{ #name_parts<#ilta #(#r_gens,)* #igena> };
let parts_s = quote!{ #name_parts<#ilta #(#s_gens,)* #igena> };
let def_defs = PERMITS.iter().map(|def| {
let def_p = format_ident!("All_{}", def);
let def_def = format_ident!("{}", def);
let defs = fields.iter().map(
|_| quote!{ #imp #def_def }
);
let q = quote!{
type #def_p = #name_parts< #ilta #( #defs, )* #igena >;
};
q
});
let fields_fields = {
let idents = fields.iter().map(|f| f.ident.as_ref().unwrap());
let numbers = 0..fields.len();
let idents2 = idents.clone();
quote!{
pub struct Fields { #( pub #idents : usize, )* }
pub const FIELDS: Fields = Fields { #( #idents2: #numbers, )* };
}
};
for (i,_f) in fields.iter().enumerate() {
let new_perms = p_gens.iter().enumerate().map(|(j,p)| {
if i == j { &vN } else { &p }
});
impls.push(quote!{
impl <#ilts #(#p_gens,)* #vN, #igens>
#imp Adjust<#vN, #i> for #parts_p #iwhere {
type Adjusted = #name_parts< #ilta #(#new_perms,)* #igena >;
}
});
}
{
for input_parts in [true,false] {
let ci_ps_none = vec![];
let (ci_ps, ci_pa) = if input_parts {
(&p_gens,
p_gens.iter().map(|i| quote!{#i}).collect_vec())
} else {
(&ci_ps_none,
iter::repeat(quote!{ #imp Mut }).take(p_gens.len()).collect_vec())
};
let conv_input = |m| mut_or_const_bind!{
m, const_;
if input_parts {
quote!{
let input = input as *#const_ _;
}
} else {
quote!{
let input = input as *#const_ _ as usize; }
}
};
let conv_output = |m, out_type: &_| mut_or_const_bind!{
m, const_ as_ref_;
quote!{
(input as *#const_ #out_type).#as_ref_().unwrap()
}
};
let ci_ty = if input_parts { &parts_p } else { &whole };
{
let mut fns = vec![];
mut_or_const!{
Deref_ IsRef_ as_ref_ AsRef_ const_ mut_sfx mut_ m;
let mc_downgrade = format_ident!("downgrade{}", mut_sfx);
let conv_input = conv_input(m);
let conv_output = conv_output(m, &parts_r);
impls.push(quote!{
impl <#ilts #(#r_gens,)* #(#ci_ps,)* #igens>
#imp #AsRef_<#parts_r>
for #ci_ty
where #( #r_gens: #imp IsDowngradeFrom<#ci_pa> ,)* #ipreds
{
fn #as_ref_(&#mut_ self) -> &#mut_ #parts_r {
#imp Downgrade::#mc_downgrade(self)
}
}
});
fns.push(quote!{
fn #mc_downgrade(input: &#mut_ Self) -> &#mut_ #parts_r {
unsafe {
#conv_input
#conv_output
}
}
});
}
impls.push(quote!{
impl <#ilts #(#r_gens,)* #(#ci_ps,)* #igens>
#imp Downgrade<#parts_r>
for #ci_ty
where #( #r_gens: #imp IsDowngradeFrom<#ci_pa> ,)* #ipreds
{ #(#fns)* }
});
}
{
let mut fns = vec![];
mut_or_const!{
Deref_ IsRef_ as_ref_ AsRef_ const_ mut_sfx mut_ m;
let mc_split_off = format_ident!("split_off{}", mut_sfx);
let conv_input = conv_input(m);
let conv_output_0 = conv_output(m, &parts_r);
let conv_output_1 = conv_output(m, "e!{ Self::Remaining });
fns.push(quote!{
fn #mc_split_off<#vlft>(input: &#vlft #mut_ #ci_ty)
-> (&#vlft #mut_ #parts_r,
&#vlft #mut_ Self::Remaining)
{
unsafe {
#conv_input
(#conv_output_0, #conv_output_1)
}
}
});
}
impls.push(quote!{
impl <#ilts #(#r_gens,)* #(#ci_ps,)* #igens>
#imp SplitOff<#parts_r>
for #ci_ty
where #( #r_gens: #imp IsDowngradeFrom<#ci_pa> ,)* #ipreds {
type Remaining = #name_parts <
#ilta
#( <#r_gens as #imp IsDowngradeFrom<#ci_pa>>::Remaining ,)*
#igena
>;
#(#fns)*
}
});
}
{
let mut fns = vec![];
mut_or_const!{
Deref_ IsRef_ as_ref_ AsRef_ const_ mut_sfx mut_ m;
let mc_split_into = format_ident!("split_into{}", mut_sfx);
let conv_input = conv_input(m);
let conv_output_0 = conv_output(m, &parts_r);
let conv_output_1 = conv_output(m, &parts_s);
impls.push(quote!{
impl <#ilts #vlft, #(#r_gens,)* #(#s_gens,)* #(#ci_ps,)* #igens>
From<&#vlft #mut_ #ci_ty>
for (&#vlft #mut_ #parts_r, &#vlft#mut_ #parts_s)
where #( #ci_pa: #imp CanSplitInto<#r_gens, #s_gens> ,)*
#ipreds
{
fn from(input: &#vlft #mut_ #ci_ty) -> Self {
#imp SplitInto::#mc_split_into(input)
}
}
});
fns.push(quote!{
fn #mc_split_into<#vlft>(input: &#vlft #mut_ #ci_ty)
-> (&#vlft #mut_ #parts_r,
&#vlft #mut_ #parts_s)
{
unsafe {
#conv_input
(#conv_output_0, #conv_output_1)
}
}
});
}
impls.push(quote!{
impl <#ilts #(#r_gens,)* #(#s_gens,)* #(#ci_ps,)* #igens>
#imp SplitInto<#parts_r, #parts_s>
for #ci_ty
where #( #ci_pa: #imp CanSplitInto<#r_gens, #s_gens> ,)* #ipreds
{ #(#fns)* }
});
}
}
impls.push(quote!{
impl<#ilts #(#p_gens,)* #igens> #imp Deref
for #parts_p
where #( #p_gens: #imp IsRef, )* #ipreds
{
type Target = #whole;
fn deref(&self) -> &Self::Target {
unsafe {
let p: *const Self = self as _;
let p: *const Self::Target = p as usize as _;
let p = p.as_ref().unwrap();
p
}
}
}
});
}
if input.debug {
let name_debug = name_parts.to_string();
impls.push(quote!{
impl<#ilts #(#p_gens,)* #igens> #imp Debug
for #parts_p
where
#(#field_types: #imp Debug,)*
#ipreds
{
fn fmt(&self, f: &mut #imp Formatter) -> #imp fmt::Result {
let mut fields = #imp Formatter::debug_struct(f, #name_debug);
#(#debug_fields)*
fields.finish()
}
}
});
}
let output = quote!{
#[allow(dead_code)] #[allow(non_camel_case_types)]
#[repr(C)]
#vis struct #name_parts <#ilts #(#p_gens,)* #igens> #iwhere {
#( #parts_fields ),*
}
impl <#ilts #igens> #imp PartialBorrow for #whole #iwhere {
#( #def_defs )*
type Fields = #name_mod::Fields;
const FIELDS: Self::Fields = #name_mod::FIELDS;
}
#(
#[allow(non_camel_case_types)]
#impls
)*
#[allow(dead_code)] #[allow(non_camel_case_types)] #[allow(non_snake_case)]
#vis mod #name_mod {
use super::#imp *;
#( #mod_fields )*
#fields_fields
}
};
let dhow = DebugVar::new();
if let Some(dhow) = dhow.mode() {
use DebugMode::*;
let mut tf = tempfile::tempfile().unwrap();
write!(tf, "{}", &output).unwrap();
tf.rewind().unwrap();
let mut tf2 = tempfile::tempfile().unwrap();
let status = Command::new("rustfmt")
.args(&["--config","tab_spaces=2,max_width=80,fn_call_width=80"])
.stdin(tf)
.stdout(tf2.try_clone().unwrap())
.status().unwrap();
assert!(status.success());
tf2.rewind().unwrap();
let mut nl_done = true;
let output = match match dhow {
Compare(_) => None,
Write(f) => Some(f),
Stderr(level) => if u8::from(level) >= 2 { Some(STDERR) } else { None },
} {
None => tempfile::tempfile().unwrap(),
Some(from) => File::create(from).expect(from),
};
let mut output = BufWriter::new(output);
let divvy = if matches!(dhow, Stderr(_)) { "\n------\n\n" } else { "" };
write!(output, "{}", divvy).unwrap();
for l in BufReader::new(tf2).lines() {
let l = l.unwrap();
let want_nl = {
let l = l.trim_start();
l.strip_prefix("pub ").unwrap_or(l);
"# impl struct fn".split(' ').any(|s| l.starts_with(s))
};
if want_nl && !nl_done { writeln!(output,"").unwrap(); nl_done=true; }
else if !want_nl { nl_done=false; }
if l.trim_end().ends_with("{") { nl_done=true; }
writeln!(output, "{}", l).unwrap();
}
write!(output, "{}", divvy).unwrap();
let mut output = output.into_inner().unwrap();
if let Compare(reference) = dhow {
output.rewind().unwrap();
eprintln!("{}: checking output against {}", DEBUG_ENVVAR, reference);
let status = Command::new("diff")
.args(&["-u","--",reference,"-"])
.stdin(output)
.stdout(File::create(STDERR).expect(STDERR))
.status().unwrap();
assert!(status.success(),
"macro ouptut changed! revise/check soundness argument!");
}
}
proc_macro::TokenStream::from(output)
}