// Copyright 2021 Ian Jackson and contributors // SPDX-License-Identifier: GPL-3.0-or-later // There is NO WARRANTY. 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, #[darling(default)] module: Option, #[darling(default)] suffix: Option, #[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, m : bool, } macro_rules! mut_or_const_bind { { $m:expr, $($local:ident)* ; $($x:tt)* } => { { // This business with the MutOrConst struct arranges to get the // members in scope as local variables, while ensuring that they // are each bound to the right thing. Ideally this would be // unhygienic but that is impossible without a proc macro... #[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 { //---------- basic input parsing ---------- 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, 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, ""); //---------- classify and collect the input generics ---------- 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 no_preds = syn::punctuated::Punctuated::new(); let ipreds = iwhere.as_ref().map(|w| &w.predicates); use format_ident as fi; //==================== start accumulating output ==================== let mut impls = vec![]; //---------- primary fields loop ---------- 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> }; // let fref_generics_act = quote!{ <#ilta #vP, #vT, #igena> }; // field refrence struct F_ 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); // impl Deref for F_ 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 { // **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 > }; // eprintln!("{}", &fref); field_types.push(fref.clone()); // field in _#vPartial 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") }, }); // generics for P, R and S 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; // Some preprepared type names, for convenience 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> }; //---------- Helpers ---------- // The All_* associated types (for use by partial!()) 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 }); // The const FIELDS structure: its type and value - the contents 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, )* }; } }; // impl Adjust<_N,i> for P 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 >; } }); } //==================== the conversion methods and impls ==================== { 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_; // Input Conversion **UNSAFE** (sets up for Output Convversion) if input_parts { quote!{ let input = input as *#const_ _; } } else { quote!{ let input = input as *#const_ _ as usize; // expose } } }; // Output Conversion **UNSAFE** (after Input Conversion) 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 }; // DOWNGRADE { 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); // impl AsRef / AsMut 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) } } }); // fn downgrade() **UNSAFE** fns.push(quote!{ fn #mc_downgrade(input: &#mut_ Self) -> &#mut_ #parts_r { unsafe { #conv_input #conv_output } } }); } // impl Downgrade **UNSAFE** 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)* } }); } // SPLIT OFF { 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 }); // fn split_off() **UNSAFE** 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) } } }); } // impl SplitOff 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)* } }); } // SPLIT INTO { 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); // impl From for P 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) } } }); // fn split_into() **UNSAFE** 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) } } }); } // impl SplitInto 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)* } }); } } // Deref impls.push(quote!{ impl<#ilts #(#p_gens,)* #igens> #imp Deref for #parts_p where #( #p_gens: #imp IsRef, )* #ipreds { type Target = #whole; // Deref for complete Partial **UNSAFE** 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 } } } }); } // Debug 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() } } }); } //==================== output ==================== 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 } }; //==================== debug/compare output ==================== 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) }