1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#![feature(proc_macro_diagnostic)]
extern crate proc_macro;
use ::darling::{FromDeriveInput, FromField, FromMeta};
use ::proc_macro::{Diagnostic, Level, TokenStream};
use ::proc_macro2::TokenStream as TokenStream2;
use ::quote::{format_ident, quote};
use ::syn::{parse_macro_input, AttributeArgs, DeriveInput};

#[derive(Debug, FromMeta)]
#[darling(default)]
struct ProjectionArgs {
	option: bool,
	result: bool,
}

#[derive(FromField)]
#[darling(forward_attrs)]
struct ProjectionField {
	ident: Option<::syn::Ident>,
	vis: ::syn::Visibility,
	ty: ::syn::Type,
	//attrs: Vec<::syn::Attribute>,
}

#[derive(FromDeriveInput)]
#[darling(supports(struct_named))]
struct ProjectionStruct {
	ident: ::syn::Ident,
	data: ::darling::ast::Data<(), ProjectionField>,
	vis: ::syn::Visibility,
}

impl Default for ProjectionArgs {
	fn default() -> Self {
		Self {
			option: true,
			result: true,
		}
	}
}

fn generate_option_impl(p: &ProjectionStruct, out: &mut TokenStream2) {
	let orig_name = p.ident.clone();
	let struct_name_base = format_ident!("__projection_internal_Option_{}_projected", p.ident);
	let vis = &p.vis;
	let mut generate = |for_mut, for_ref| {
		use ::darling::ast::Data::*;
		let fields = match &p.data {
			Enum(_) => panic!(),
			Struct(s) => s,
		};
		let struct_name = if for_mut {
			format_ident!("{}_ref_mut", struct_name_base)
		} else if for_ref {
			format_ident!("{}_ref", struct_name_base)
		} else {
			struct_name_base.clone()
		};
		let mut_token = if for_mut {
			quote! { mut }
		} else {
			quote! {}
		};
		let ref_token = if for_ref {
			quote! { & }
		} else {
			quote! {}
		};
		let lifetime_token = if for_ref {
			quote! { 'a }
		} else {
			quote! {}
		};
		let projected_fields: TokenStream2 = fields
			.iter()
			.map(|f| {
				let ident = f.ident.as_ref().unwrap();
				let ty = &f.ty;
				let vis = &f.vis;
				quote! { #vis #ident : Option<#ref_token #lifetime_token #mut_token #ty>, }
			})
			.collect();
		let projected_fields_init: TokenStream2 = fields
			.iter()
			.map(|f| {
				let ident = f.ident.as_ref().unwrap();
				quote! { #ident: Some(#ref_token #mut_token f.#ident), }
			})
			.collect();
		out.extend(quote! {
			#[derive(Default)]
			#[allow(non_camel_case_types)]
			#vis struct #struct_name<#lifetime_token> {
				#projected_fields
			}
			impl<#lifetime_token> From<#ref_token #lifetime_token #mut_token #orig_name> for
			    #struct_name<#lifetime_token> {
				fn from(f: #ref_token #lifetime_token #mut_token #orig_name) -> Self {
					Self {
						#projected_fields_init
					}
				}
			}

			impl<#lifetime_token> ::projection::OptionProjectable for
			    #ref_token #lifetime_token #mut_token #orig_name {
				type P = #struct_name<#lifetime_token>;
				fn project(f: Option<Self>) -> Self::P {
					match f {
						Some(t) => t.into(),
						None => Default::default(),
					}
				}
			}
		});
	};
	generate(false, true);
	generate(true, true);
	generate(false, false);
}

fn generate_result_impl(_p: &ProjectionStruct, _out: &mut TokenStream2) {}

#[proc_macro_attribute]
pub fn projection(args: TokenStream, input: TokenStream) -> TokenStream {
	fn imp(attr: AttributeArgs, input: DeriveInput) -> Result<TokenStream, ::darling::Error> {
		use ::quote::ToTokens;
		let project = ProjectionStruct::from_derive_input(&input)?;
		let project_args = ProjectionArgs::from_list(&attr)?;
		let mut ret = TokenStream::new().into();
		if project_args.option {
			generate_option_impl(&project, &mut ret);
		}
		if project_args.result {
			generate_result_impl(&project, &mut ret);
		}
		input.to_tokens(&mut ret);
		Ok(ret.into())
	}
	let attr = parse_macro_input!(args as AttributeArgs);
	let input = parse_macro_input!(input as DeriveInput);
	match imp(attr, input) {
		Ok(stream) => stream,
		Err(e) => {
			Diagnostic::new(Level::Error, e.to_string()).emit();
			TokenStream::new()
		}
	}
}